题意:小H最近迷上了一个分割序列的游戏。在这个游戏里,小H需要将一个长
度为N的非负整数序列分割成k+l个非空的子序列。为了得到k+l个子序列,
小H将重复进行七次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的
序列一一也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新
序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序
列中元素和的乘积。小H希望选择一种最佳的分割方案,使得k轮(次)之后,
小H的总得分最大。
容易发现分割的顺序和答案无关,令f[i][k]表示前i个数被分成k段获得的最大分数,f[i][k]=max(f[j][k-1]+sum[i]*sum[j]-sum[j]^2).
这个式子明显可以斜率优化搞,但这题有个坑的地方,a[i]可以为0,所以会出现sum[i]=sumj,直线求交的时候要注意特判。
Tips:WA了2发,就是因为0…,还要滚动数组.
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=100000+10;
long long f[maxn][3],sum[maxn],ans;
int n,m,q[maxn],head,tail,a[maxn];
double calc(int x,int y,int k)
{
double res=(double)(f[x][(k-1)%2]-f[y][(k-1)%2]-sum[x]*sum[x]+sum[y]*sum[y])/(double)(sum[y]-sum[x]);
return res;
}
int main()
{
//freopen("3675.in","r",stdin);
//freopen("3675.out","w",stdout);
scanf("%d%d",&n,&m);m++;
int num=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
if(a[i]!=0) a[++num]=a[i];
n=num;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;i++) f[i][1]=0;
for(int k=2;k<=m;k++)
{
q[head=tail=1]=k-1;
for(int i=k;i<=n;i++)
{
while(head<tail&&calc(q[head],q[head+1],k)<sum[i]) head++;
f[i][k%2]=f[q[head]][(k-1)%2]+(sum[i]-sum[q[head]])*sum[q[head]];
ans=max(ans,f[i][k%2]);
while(head<tail&&calc(q[tail],i,k)<calc(q[tail-1],q[tail],k)) tail--;
q[++tail]=i;
}
}
printf("%lld\n",ans);
return 0;
}