[APIO2014]序列分割

 此题我看到第一眼的想法是

区间DP,复杂度大约O(n^4)

但是我在推样例的时候发现了一个重要的性质

最后的结果只与切的位置有关而与切的顺序无关

将状态设计为f[i][j]表示前i个数,切j刀,第j刀切在i处所最得到的最大的分

由此我们可以推出DP式子:f[i][k+1]=f[j][k]+sum[j]*(sum[i]-sum[j]);

但这样的复杂度仍然不对

看到式子的形状,我们似乎可以采用斜率优化

f[j][k]-sum[j]*sum[j]=f[i][k+1]-sum[i]*sum[j];
这里我们要注意两个点在同一个横坐标的情况
另外我们只用开一个队列Q
因为虽然转移得到的截距为k+1的,但是点(x,y)都是k的
实现如下:
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;
inline ll read()
{
    register ll p(1),a(0);register char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') p=-1,ch=getchar();
    while(ch>='0'&&ch<='9') a=a*10+ch-48,ch=getchar();
    return a*p;
}
//f[i][k]=f[j][k-1]+sum[j]*(sum[i]-sum[j]);
//f[j][k-1]-sum[j]*sum[j]=f[i][k]-sum[i]*sum[j];
const ll N=100100,M=210,INF=0x3f3f3f3f;
ll n,kk,f[N][M],to[N][M],sum[N],Q[N],head=0,tail=0;
inline long double getx(int x){return sum[x];}
inline long double gety(int x,int now){return f[x][now]-sum[x]*sum[x];}
inline long double che(int x,int y,int now)
{return getx(y)==getx(x)?-INF:(gety(y,now)-gety(x,now))/(getx(x)-getx(y));}
inline void pri(int xx,int yy){if(xx)pri(to[xx][yy],yy-1),printf("%lld ",xx);}
int main()
{
    freopen("input","r",stdin);
    freopen("output","w",stdout);
    n=read(),kk=read();
    for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+read();
    for(register int k=1;k<=kk;k++,head=tail=0)
        for(register int i=1,j;i<=n;i++)
            {
                while(head<tail&&che(Q[head],Q[head+1],k)<=sum[i]) ++head;
                j=Q[head];f[i][k+1]=f[j][k]+sum[j]*(sum[i]-sum[j]);
                to[i][k+1]=j;
                while(head<tail&&che(Q[tail-1],Q[tail],k)>=che(Q[tail],i,k)) --tail;
                Q[++tail]=i;
            }
    printf("%lld\n",f[n][kk+1]);
    pri(to[n][kk+1],kk);
    return 0;
}
/*

*/

 

posted @ 2018-12-15 23:48  cold_cold  阅读(153)  评论(0编辑  收藏  举报