[斜率优化][DP]luogu P3648 序列分割
https://www.luogu.org/problemnew/show/P3648
分析
首先我们先证明,分割的顺序与答案无关
设有三段a,b,c
a(b+c)+bc=ab+ac+bc①
ab+(a+b)c=ab+ac+bc②
①=②
假装证明了
然后我们设f[i][k]表示第i个数字的后面分割,共分割了k次的最大分数
f[i][k]=min(f[j][k-1]+s[j]*(s[i]-s[j])) (j<i) s为前缀和
这个比较显然的斜率优化柿子
单调队列维护一个下凸壳即可
#include <iostream> #include <cstdio> #include <memory.h> using namespace std; typedef long long ll; const int N=1e5+10; int h,t,q[N]; ll f[N][2],s[N],from[310][N]; int n,k; double XL(int i,int j,int k) { if (s[k]-s[j]==0) return -2147483647; return 1.0*(f[j][(i+1)&1]-s[j]*s[j]-f[k][(i+1)&1]+s[k]*s[k])/(s[k]-s[j]); } int main() { scanf("%d%d",&n,&k); for (int i=1;i<=n;i++) scanf("%lld",&s[i]),s[i]+=s[i-1]; for (int j=k-1;j>=0;j--) { q[h=t=1]=0; for (int i=1;i<=n;i++) { while (h<t&&XL(j,q[h],q[h+1])<=s[i]) h++; f[i][j&1]=f[q[h]][(j+1)&1]+s[q[h]]*(s[i]-s[q[h]]); from[j][i]=q[h]; while (h<t&&XL(j,q[t-1],q[t])>=XL(j,q[t],i)) t--; q[++t]=i; } } int x=n,j=0; printf("%lld\n",f[n][0]); while (j<k) { x=from[j][x]; printf("%d ",x); j++; } }
在日渐沉没的世界里,我发现了你。