[APIO2014]序列分割

洛咕

题意:试将一个长度为\(N(N<=100000)\)的序列分成\(M+1\)块,每一次选择一个有超过一个元素的块(初始时你只有一块,即整个序列)选择两个相邻元素把这个块从中间分开,得到两个非空的块.每次操作后你将获得那两个新产生的块的元素和的乘积的分数.求最大总得分,并输出任意一种方案.

分析:首先我们猜一个结论,就是对于一个序列,我们割的顺序不影响最后的得分.

非正规非严谨证明:假设一个序列最后被我们分成了\(a,b,c\)三块,那么不论是先分成\(a\)\(b,c\),还是先分成\(a,b\)\(c\),最后的得分都是\(a*(b+c)+b*c=(a+b)*c+a*b=ab+ac+bc\).证毕.

既然与顺序无关,那我们就假设是从前往后分的.设\(f[i][j]\)表示将前i个数分成j块的最大得分,最后的答案应该是\(f[n][m+1]\),则有

\(f[i][j]=f[k][j-1]+(sum[i]-sum[k])*(sum[n]-sum[i])\)其中\(k<i\).

\(l<k\)且k比l更优,即

\(f[k][j-1]+(sum[i]-sum[k])*(sum[n]-sum[i])>f[l][j-1]+(sum[i]-sum[l])*(sum[n]-sum[i])\)

整理一下这个式子得到,

\(\frac{f[k][j-1]-f[l][j-1]}{sum[k]-sum[l]}>sum[n]-sum[i]\)

就可以愉快地斜率优化了.

一开始以为是被卡常了,最后几个大数据点一直超时,然后各种卡常,滚动数组,最后好像主要是因为中间那几个乘积爆了\(int\),乘上\(1ll\)就好了.

题目还要求输出任意一种方案,根据套路,再开一个pre数组,每次转移时记录一下从哪里转移来的,最后从\(pre[n][m+1]\)往前找就行了.

#include<bits/stdc++.h>
#define N 100005
#define M 205
#define LL long long
#define rg register
using namespace std;
inline int read(){
   int s=0,w=1;char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
   return s*w;
}
int q[N],pre[N][M],sum[N];LL f[N][2];
int main(){
    int n=read(),m=read();
    for(rg int i=1;i<=n;++i)sum[i]=read(),sum[i]+=sum[i-1];
    for(rg int j=1;j<=m+1;++j){
		rg int l=1,r=1;q[1]=0;
		for(rg int i=1;i<=n;++i){
	    	while(l<r&&(f[q[l+1]][(j-1)&1]-f[q[l]][(j-1)&1])>=(1ll*(sum[n]-sum[i])*(sum[q[l+1]]-sum[q[l]])))l++;
	    	f[i][j&1]=f[q[l]][(j-1)&1]+1ll*(sum[i]-sum[q[l]])*(sum[n]-sum[i]);
	    	pre[i][j]=q[l];
	    	while(l<r&&(1ll*(f[q[r]][(j-1)&1]-f[q[r-1]][(j-1)&1])*(sum[i]-sum[q[r]]))<=(1ll*(f[i][(j-1)&1]-f[q[r]][(j-1)&1])*(sum[q[r]]-sum[q[r-1]])))r--;
	    	q[++r]=i;
		}
    }
    printf("%lld\n",f[n][(m+1)&1]);
    for(rg int i=m+1;i>=2;--i){
		printf("%d ",pre[n][i]);
        n=pre[n][i];
    }
    return 0;
}

posted on 2019-06-12 17:17  PPXppx  阅读(116)  评论(0编辑  收藏  举报