BZOJ 3675: [Apio2014]序列分割

来源:互联网 发布:娱乐栏目名称有趣 知乎 编辑:程序博客网 时间:2024/05/17 22:34

题意:小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;}
0 0
原创粉丝点击