Print Article
Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 6653 Accepted Submission(s): 2054
One day Zero want to print an article which has N words, and each word i has a cost Ci to be printed. Also, Zero know that print k words in one line will cost
M is a const number.
Now Zero want to know the minimum cost in order to arrange the article perfectly.
我们首先假设在算 dp[i]时,k<j ,j点比k点优。
也就是
dp[j]+(sum[i]-sum[j])^2+M <= dp[k]+(sum[i]-sum[k])^2+M;
所谓j比k优就是DP方程里面的值更小
对上述方程进行整理很容易得到:
[(dp[j]+sum[j]*sum[j])-(dp[k]+sum[k]*sum[k])] / 2(sum[j]-sum[k]) <=sum[i].
注意整理中要考虑下正负,涉及到不等号的方向。
左边我们发现如果令:yj=dp[j]+sum[j]*sum[j] xj=2*sum[j]
那么就变成了斜率表达式:(yj-yk)/(xj-xk) <= sum[i];
而且不等式右边是递增的。
所以我们可以看出以下两点:我们令g[k,j]=(yj-yk)/(xj-xk)
第一:如果上面的不等式成立,那就说j比k优,而且随着i的增大上述不等式一定是成立的,也就是对i以后算DP值时,j都比k优。那么k就是可以淘汰的。
如果不成立,那就说明k比j优,但是随着i的增加sum[i]增加,j始终会替换掉k.
第二:如果 k<j<i 而且 g[k,j]>g[j,i] 那么 j 是可以淘汰的。
假设 g[j,i]<sum[i]就是i比j优,那么j没有存在的价值
相反如果 g[j,i]>sum[i] 那么同样有 g[k,j]>sum[i] 那么 k比 j优 那么 j 是可以淘汰
所以这样相当于在维护一个下凸的图形,斜率在逐渐增大。
通过一个队列来维护。
初始化队列:head=0,tail=0;que[tail++]=0;表示tail处不存决策。
于是对于这题我们对于斜率优化做法可以总结如下:
1,用一个单调队列来维护解集。
2,假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的上凸性质,即如果g[d,c]<g[c,b],那么就将c点删除。直到找到g[d,x]>=g[x,y]为止,并将d点加入在该位置中。
3,求解时候,从队头开始,如果已有元素a b c,当i点要求解时,如果g[b,a]<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队。最后dp[i]=getDp(q[head]);
#include <iostream> #include <cstdio> #include <string.h> #include <math.h> #define maxn 500010 #define LL int using namespace std; LL sum[maxn],dp[maxn]; int que[maxn]; int head,tail; int n; LL m; int getdp(int i,int j) { return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]); } int getup(int j,int k) //yj-yk部分 { return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]); } int getdown(int j,int k) { return 2*(sum[j]-sum[k]); } void solve() { head=0; tail=0; que[tail++]=0; //队列里存储的是决策 //tail处不存决策 for(int i=1;i<=n;i++) { //从头开始找当前状态的最优决策,g[que[head+1],que[head]] < sum[i],说明que[head+1]比que[head]更优,删除que[head] while(head+1 < tail && getup(que[head+1],que[head]) <= getdown(que[head+1],que[head]) * sum[i] ) head++; //注意写成相乘,不然要考虑除数是否为负数 dp[i]=getdp(i,que[head]); //从尾往前,加入当前状态,如果g[i,que[tail]] < g[que[tail],que[tail-1]] ,可以排除que[tail] /* while(head+1 <tail && getup(i,que[tail-1]) * getdown(que[tail-1],que[tail-2]) <= getup(que[tail-1],que[tail-2])*getdown(i,que[tail-1]))) { tail--; //看到为什么RE了吗?笨蛋,括号打错了 }*/ while(head+1<tail && getup(i,que[tail-1])*getdown(que[tail-1],que[tail-2])<=getup(que[tail-1],que[tail-2])*getdown(i,que[tail-1])) tail--; que[tail++]=i; } /*for(int i=1;i<=n;i++) printf("%.0lf ",dp[i]);*/ printf("%d ",dp[n]); } int main() { while(scanf("%d%d",&n,&m)==2) { // init(); sum[0]=0; for(int i=1;i<=n;i++) { scanf("%d",&sum[i]); sum[i]+=sum[i-1]; } //for(int i=1;i<=n;i++) // printf("%lf ",sum[i]); solve(); } return 0; }