@ysner
2018-08-02T11:37:19.000000Z
字数 1690
阅读 3093
DP
给定一段长度为的序列,需要把它分为任意多段。
对于每一段,需要选出一个数,若在该段中出现次,则该段贡献为。
最大化贡献和。
orz GXZlegend
显然有个性质:每一段的两端同为。
因为两端多出的数不可能对该段有贡献,提出它们以期望对其他段产生贡献,才能实现贡献最大化。
则可设状态为表示前个数产生的最大贡献,表示在第个位置这个数是第几次出现(出现次数的前缀和)。
有方程式
原式为
#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<cmath>#include<algorithm>#include<vector>#define re register#define il inline#define ll long long#define max(a,b) ((a)>(b)?(a):(b))#define min(a,b) ((a)<(b)?(a):(b))#define fp(i,a,b) for(re int i=a;i<=b;i++)#define fq(i,a,b) for(re int i=a;i>=b;i--)using namespace std;const int N=1e5+100;ll n,c[N],s[N],num[N],x[N],y[N],dp[N];vector<int>Q[N/10];il ll gi(){re ll x=0,t=1;re char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();if(ch=='-') t=-1,ch=getchar();while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();return x*t;}il double slope(re int a,re int b){ return ((double)(y[a]-y[b]))/(x[a]-x[b]);}int main(){n=gi();fp(i,1,n) c[i]=gi(),s[i]=++num[c[i]];fp(i,1,n){re int col=c[i],top=Q[col].size()-1;x[i]=(s[i]-1)*col;y[i]=x[i]*(s[i]-1)+dp[i-1];while(top>0&&slope(Q[col][top-1],Q[col][top])<slope(Q[col][top],i)) Q[col].pop_back(),--top;Q[col].push_back(i);++top;while(top>0&&slope(Q[col][top-1],Q[col][top])<2*s[i]) Q[col].pop_back(),--top;re int t=Q[col][top];dp[i]=dp[t-1]+col*(s[i]-s[t]+1)*(s[i]-s[t]+1);}printf("%lld\n",dp[n]);return 0;}
