序列分割题解
序列分割题解
看上去是区间\(DP\)题,实则不然:
毕竟区间\(DP\) \(O(n^3)\)的复杂度难以优化,
那么,我们是不是可以想到,
每一块的贡献是否与切割顺序无关?
证明一下:
假设区间\([l_{1},r_{3}]\),被分割为\([l_{1},r_{1}],[l_{2},r_{2}],[l_{3},r_{3}]\),
我们算一算\([l_{1},r_{1}]\)的贡献,
先分割\(r_{1},l_{2}\),再分割\(r_{2},l_{3}\),只有一次贡献,为:\((sum[r_{1}]-sum[l_{1}-1])*(sum[r_{3}]-sum[r_{1}])\)
先分割\(r_{2},l_{3}\),再分割\(r_{1},l_{2}\),有两次贡献,为:\((sum[r_{1}]-sum[l_{1}-1])*[(sum[r_{3}]-sum[r_{2}])+(sum[r_{2}]-sum[r_{1}])]=(sum[r_{1}]-sum[l_{1}-1])*(sum[r_{3}]-sum[r_{1}])\)
也可以感性理解一下,
每种情况都可视为将\([l,r]\)后面的\([r+1,n]\)从右至左分成k段,其贡献为:\((sum[r]-sum[l-1])*\sum_{i=1}^{k}(sum[r_{i}]-sum[r_{i-1}])=(sum[r]-sum[l-1])*(sum[n]-sum[r])\)
我们就可以用费用前移算出贡献了。
所以,暴力线性DP方程为:\(f[i][j]=f[i-1][k]+(sum[j]-sum[k])*(sum[n]-sum[j])\)
拆括号,移项:\(f[i-1][k]=sum[k]*(sum[n]-sum[j])+f[i][j]-(sum[n]-sum[j])*sum[j]\)
\(\bullet y=f[i-1][k]\)
\(\bullet k=sum[n]-sum[j]\)
\(\bullet x=sum[k]\)
\(\bullet b=f[i][j]-(sum[n]-sum[j])*sum[j]\)
由于k递减但大于0,x递增,求最大值,我们可以维护这样的凸壳:
接下来就是斜率优化的板子了。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100006,K=206;
int o=1,n,k,head=1,tail=1,pre[K][N];
ll t,f[K][N],sum[N];
struct point{ll x,y; int p;}tmp,q[N];
bool check(point u,point v,int z){return v.y-u.y>=(sum[n]-sum[z])*(v.x-u.x);}
bool check2(point u,point v,point z){return (v.y-u.y)*(z.x-v.x)<=(z.y-v.y)*(v.x-u.x);}
inline int read(){
int T=0,F=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
return F*T;
}
void dfs(int u,int v){
if(!u) return;
dfs(u-1,pre[u][v]);
if(v!=n) printf("%d ",v);
}
int main(){
n=read(),k=read(),++k;
for(int i=1;i<=n;++i) t=read(),sum[i]=sum[i-1]+t;
for(int j=1;j<=k;++j){
q[1].x=q[1].y=q[1].p=0,head=tail=1;
for(int i=1;i<=n;++i){
while(head<tail&&check(q[head],q[head+1],i)) ++head;
pre[j][i]=q[head].p,f[j][i]=q[head].y+(sum[n]-sum[i])*(sum[i]-q[head].x),tmp.x=sum[i],tmp.y=f[j-1][i],tmp.p=i;
while(head<tail&&check2(q[tail-1],q[tail],tmp)) --tail;
q[++tail]=tmp;
}
}
printf("%lld\n",f[k][n]);
dfs(k,n);
return 0;
}