5 5 5 9 5 7 5
230
给出N和M,有N个单词,接下来N行为C[i](1<=i<=N),连续打印一段的花费是这一段C的和的平方加上M,问打印整个文章的最小花费。
dp[i]表示前i个单词的最小花费,sum[i]=c[1]+...+c[i],于是dp[i]=max(dp[j]+(sum[i]-sum[j])^2+M)(j<i),对于某个j,展开式子,dp[i]=dp[j]+sum[i]^2+sum[j]^2-2*sum[i]*sum[j]+M,对于另一个k,如果j比k优的话有dp[j]+sum[i]^2+sum[j]^2-2*sum[i]*sum[j]+M<=dp[k]+sum[i]^2+sum[k]^2-2*sum[i]*sum[k]+M,移项化简得(dp[j]+sum[j]^2-(dp[k]+sum[k]^2))/(2*(sum[j]-sum[k]))<=sum[i],把设yj=dp[j]+sum[j]^2,xj=sum[j],那么只需要维护一个斜率越来越大的曲线,也就是下凸曲线(如果上凸,三个点的话中间的点肯定不是最优),每次找到队列中斜率尽量大但是小于等于sum[i]的相邻的两个点,由此得到dp[i],再把i点加入队列。由于sum[i]是逐渐增大的,所以从头到尾只用维护一遍这个队列。
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<vector> #include<cmath> #include<queue> #include<stack> #include<map> #include<set> #include<algorithm> #pragma comment(linker, "/STACK:102400000,102400000") using namespace std; const int MAXN=500010; const int MAXM=100010; int N,M; int dp[MAXN],q[MAXN],sum[MAXN]; int y(int j,int i){ return dp[i]+sum[i]*sum[i]-(dp[j]+sum[j]*sum[j]); } int x(int j,int i){ return 2*(sum[i]-sum[j]); } int main(){ freopen("in.txt","r",stdin); while(scanf("%d%d",&N,&M)!=EOF){ int t; sum[0]=0; for(int i=1;i<=N;i++){ scanf("%d",&t); sum[i]=sum[i-1]+t; } dp[0]=0; int front=0,rear=0; q[0]=0; for(int i=1;i<=N;i++){ while(front<rear&&y(q[front],q[front+1])<=x(q[front],q[front+1])*sum[i]) front++; dp[i]=dp[q[front]]+(sum[i]-sum[q[front]])*(sum[i]-sum[q[front]])+M; while(front<rear&&y(q[rear],i)*x(q[rear-1],q[rear])<=y(q[rear-1],q[rear])*x(q[rear],i)) rear--; q[++rear]=i; } printf("%d\n",dp[N]); } return 0; }