这个题乍一看挺复杂的,我们首先需要解决一个问题,一个影响我们决策的性质。
根据DP的定义和转移我们可以用线性DP最常用的模板
f[i][k] = f[j][k-1] + sum(j,i) sum(j,i)为这次新产生的价值
对应到本题中我们发现我们的决策点J也就是去切割的序列,自然J - I 是一部分,那剩下一部分呢?我一下子想到了在枚举一个左端点,其实错误(没必要)的,我们发现当前我砍了一刀,将一个序列分成了两份,然后用剩下序列分成K-1份来转移,这道题有个较为显然的性质,即无论按照什么顺序切割,最终值都是相等的,所以我们可以直接用(s[i] - s[j]) * s[j],(s为前缀和)
我们可以考虑每个点的贡献发现顺序无论怎么变他的贡献都是一定的。
方程为:f[i][k] = max(f[i][k],f[j][k-1]+(s[i]-s[j])*s[j])
有J,I都具有的项,很明显的斜率优化。
拆式子:f[i][k] = f[j][k-1] + s[i]s[j] - s[j]s[j]
转化为一般形式:X = s[j], K = s[i] ,Y = -f[j][k-1]+s[i]s[i],b = f[i][k]
斜率单调递增,维护一个下凸包,当队头比现在的斜率小,弹对头
当队尾比现在的斜率大,弹队尾(画图)
然后注意一下输出和滚动数组K&1,(K-1)&1,主意好斜率是的P
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <stack> using namespace std; #define int long long #define N 100001 int f[N][2];//滚动数组滚一维 int n, K; int a[N], s[N]; int q[N]; int pre[N][201]; double slope(int x,int y,int l){ if(s[x]==s[y]) return -1e18; return (double)((s[x]*s[x]) - (s[y]*s[y])-f[x][(l-1)&1] + f[y][(l-1)&1]) / (double) (s[x]-s[y]); } signed main(){ cin >> n >> K; for(int i = 1;i <= n;++ i) cin >> a[i]; for(int i = 1;i <= n;++ i) s[i] = s[i-1] + a[i]; /*考试时候错误想法,保存下来 //显然我们发现,无论怎么切割,当前这个状态的右端点始终是n不变 //所以我们第一重循环枚举一个序列的起点,第二重循环枚举一下当前的切割点 //i=1就是序列本身 //j=n就是单独把最后一个点拎出来 //在枚举一下我们希望切割城的序列数量 */ int h = 0,t = 0, ans = -1; for(int j = 1;j <= K;++ j){ h = t = 0; for(int i = 1;i <= n;++ i){ while(h < t && slope(q[h], q[h+1], j) <= s[i]) h ++; f[i][j&1] = max(f[i][j&1], f[q[h]][(j-1)&1] + s[q[h]] * (s[i]-s[q[h]])); pre[i][j] = q[h]; while(h < t && slope(q[t], q[t-1], j) >= slope(q[t],i,j)) t --; q[++ t]= i; } } cout << f[n][K&1] << endl; vector<int> v; while (K){ v.push_back(pre[n][K]); n = pre[n][K]; --K; } for(int i = v.size()-1;i >= 0;-- i) cout << v[i] << ' '; return 0; }