[关闭]
@ysner 2018-08-02T11:37:19.000000Z 字数 1690 阅读 2128

[JSOI2011]柠檬

DP


题面

给定一段长度为的序列,需要把它分为任意多段。
对于每一段,需要选出一个数,若在该段中出现次,则该段贡献为
最大化贡献和。

解析

orz GXZlegend
显然有个性质:每一段的两端同为
因为两端多出的数不可能对该段有贡献,提出它们以期望对其他段产生贡献,才能实现贡献最大化。

则可设状态为表示前个数产生的最大贡献,表示在第个位置这个数是第几次出现(出现次数的前缀和)。
有方程式


看起来很虚啊。
这时就要考虑斜率优化了。

原式为


于是“参变量分离”,与有关的项放到项,无关的放到项。
于是化为


其中
我们可以维护一个单调栈,栈顶维护最优决策。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<algorithm>
  7. #include<vector>
  8. #define re register
  9. #define il inline
  10. #define ll long long
  11. #define max(a,b) ((a)>(b)?(a):(b))
  12. #define min(a,b) ((a)<(b)?(a):(b))
  13. #define fp(i,a,b) for(re int i=a;i<=b;i++)
  14. #define fq(i,a,b) for(re int i=a;i>=b;i--)
  15. using namespace std;
  16. const int N=1e5+100;
  17. ll n,c[N],s[N],num[N],x[N],y[N],dp[N];
  18. vector<int>Q[N/10];
  19. il ll gi()
  20. {
  21. re ll x=0,t=1;
  22. re char ch=getchar();
  23. while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  24. if(ch=='-') t=-1,ch=getchar();
  25. while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  26. return x*t;
  27. }
  28. il double slope(re int a,re int b){ return ((double)(y[a]-y[b]))/(x[a]-x[b]);}
  29. int main()
  30. {
  31. n=gi();
  32. fp(i,1,n) c[i]=gi(),s[i]=++num[c[i]];
  33. fp(i,1,n)
  34. {
  35. re int col=c[i],top=Q[col].size()-1;
  36. x[i]=(s[i]-1)*col;y[i]=x[i]*(s[i]-1)+dp[i-1];
  37. while(top>0&&slope(Q[col][top-1],Q[col][top])<slope(Q[col][top],i)) Q[col].pop_back(),--top;
  38. Q[col].push_back(i);++top;
  39. while(top>0&&slope(Q[col][top-1],Q[col][top])<2*s[i]) Q[col].pop_back(),--top;
  40. re int t=Q[col][top];
  41. dp[i]=dp[t-1]+col*(s[i]-s[t]+1)*(s[i]-s[t]+1);
  42. }
  43. printf("%lld\n",dp[n]);
  44. return 0;
  45. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注