P3648 [APIO2014] 序列分割

对于这道题,我们很容易想出一个暴力 DP

\(f_{i,j,k}\) 表示将区间 \([i,j]\) 切割 \(k\) 次的最大得分,\(s_i\) 表示 \(a_i\) 的前缀和。

我们可以得到一个式子:

\[f_{i,j,k} = \max_{i\leq m < j,p < k}\{f_{i,k.p} + f_{k+1,j,p} + (s_k-s_{i-1}) * (s_j-s_k)\} \]

显然,这个式子的复杂度是 \(O(n^3k^2)\) 的,直接爆炸。

这么大的差距,我们肯定得去找个性质来优化。(明显只有找到性质才能逼近正解)

我们考虑区间 \(A,B,C\) 的合并,我们记 \(a,b,c\) 分别为三个区间的区间和。

  • \(AB~|~C \to (a+b) \times c + a \times b = ab + ac + bc\)
  • \(A~|~BC \to a\times (b+c) + b \times c = ab + ac + bc\)

所以说,从上面的式子来看,我们其实并不需要关心将块分开的顺序,只需要关心从哪些位置分开。

因此,我们便可以将状态简化为:设 \(f_{i,j}\) 表示将前 \(i\) 个数切割 \(j\) 次的最大收益,并可以得到式子如下:

\[f_{i,j} = \max_{k < i}\{f_{k,j-1} + s_k \times (s_i-s_k)\} \]

我们可以发现,这个式子满足 \(y = kx +b\) 的性质,可以进行斜率优化。

写成斜率优化的式子:

\[f_{k,j-1} - s_k^2= -s_k\times s_i + f_{i,j} \]

因为是求 \(b\) 的最大值,所以维护上凸包。

最后输出方案只需要每次将决策点记下即可。

代码要过的话,细节有点多,需要好好调。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;
int n,k;
int j;
ll a[NN],s[NN];
ll f[NN];
ll h[NN];
ll g[NN][208];
ll ans[NN];
int que[NN],head,tail;
inline double Slope(int x,int y){if(s[x] == s[y]) return 1e-18;return ((h[x] - s[x] * s[x])-(h[y] - s[y] * s[y])) / (double)(s[x]-s[y]);}
int main(){
//	freopen("1.in","r",stdin);
	scanf("%lld%lld",&n,&k);
	for(int i = 1; i <= n; ++i) scanf("%lld",&a[i]),s[i] = s[i-1] + a[i];
	for(j = 1; j <= k; ++j){
		head = 1;tail = 0;
		que[++tail] = 0;
		for(int i = 1; i <= n; ++i) h[i] = f[i];
		for(register int i = 1; i <= n; ++i){
			while(head < tail && Slope(que[head],que[head+1]) >= -s[i]) ++head;
			int k = que[head];
			g[i][j] = k;
			f[i] = h[k] + s[k] * (s[i] - s[k]);
			while(head < tail && Slope(que[tail],que[tail-1]) <= Slope(que[tail],i)) --tail;
			que[++tail] = i;
		}
	}
	printf("%lld\n",f[n]);
	int now = n,pos = 1;
	for(int i = k; i >= 1; --i){
		now = ans[i] = g[now][i];
		if(now == 0) {pos = i+1;break;}
	}
	for(int i = pos; i <= k; ++i) printf("%lld ",ans[i]);
}
posted @ 2023-09-03 22:07  ricky_lin  阅读(29)  评论(0)    收藏  举报