BZOJ 3675: [Apio2014]序列分割

题意:小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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值