【BZOJ】3675: [Apio2014]序列分割

http://www.lydsy.com/JudgeOnline/problem.php?id=3675

题意:给一个n个数字的序列,每一次分割的贡献是$sum(left, mid)*sum(mid+1, right)$,其中$left$表示本序列的最左边,$right$同理,$mid$是分割的位置(即在$mid$和$mid+1$中分割)。每次分割序列会变成两半。问分割k次得到的最大贡献和。n<=100000, k<=200

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
ll s[N], d[2][N];
int n, K, q[N], fr, ta;
int main() {
	scanf("%d%d", &n, &K);
	for(int i=1; i<=n; ++i) scanf("%lld", &s[i]), s[i]+=s[i-1];
	ll *now=d[0], *last=d[1];
	for(int p=1; p<=K; ++p) {
		fr=ta=0; q[ta++]=0;
		for(int i=1; i<=n; ++i) {
			while(fr!=ta-1 && last[q[fr]]-last[q[fr+1]]<=(s[q[fr]]-s[q[fr+1]])*(s[n]-s[i])) fr++;
			int j=q[fr];
			now[i]=last[j]+(s[i]-s[j])*(s[n]-s[i]);
			while(fr!=ta-1 && (last[i]-last[q[ta-2]])*(s[q[ta-1]]-s[q[ta-2]])>=(last[q[ta-1]]-last[q[ta-2]])*(s[i]-s[q[ta-2]])) --ta;
			q[ta++]=i;
		}
		swap(last, now);
	}
	ll ans=0;
	for(int i=1; i<=n; ++i) ans=max(ans, last[i]);
	printf("%lld
", ans);
	return 0;
}

  

听laekov说要分析一下特殊的性质,于是分析了一下。。可以发现,每一个块对答案的贡献是$sum(本块)*sum(剩下的元素)$,最后当然要除以2,因为重复算了两次。但是可以用乱搞一下。。。

考虑dp,设$d(i, j)$表示这个序列在$i$分割了$j$次得到的答案且第$j$次是在$i$分割的。

容易得到:

$d(i, j)=max(d(k, j-1)+sum(k+1, i)*sum(i+1, n))$

大概就是表示得到的块为$(k+1, i)$,然后由于前面算过了对这些值的乘积,所以不用再计算一次(否则答案要除以二= =),于是我们直接乘一下后面的和即可。。

然后另$s(n)=sum_{i=1}^{n} a[i]$,则

$d(i, j)=max(d(k, j-1)+(s(i)-s(k))*(s(n)-s(i)))$

然后搞搞斜率优化即可= =

(好久没写了然后发现自己维护上凸壳时整个人sb了。。。。队尾居然不是维护凸壳然后交了4发wa居然没发现。。

原文地址:https://www.cnblogs.com/iwtwiioi/p/4488743.html