[bzoj 3675--Apio2014]序列分割

124 篇文章 2 订阅
26 篇文章 0 订阅

小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。

(这道题告诉我,公式一定不要推错)
很明显,这题是dp,但一定不是普通·dp。方程很简单,f[i][k]=max(f[j][k-1]+(s[i]-s[j])*s[j])(分割顺序不影响,伟大的乘法分配律),再看,只发现可以用斜率优化。
设i>j>w
f[j][k-1]+(s[i]-s[j])*s[j]>f[w][k-1]+(s[i]-s[w])*s[w]
化得
(s[j]^2-f[j][k-1]+s[w]^2-f[w][k-1])/(s[j]-s[w])< s[i]
设Y[i]=s[i]^2-f[i][k-1],X[i]=s[i]
(Y[j]-Y[w])/(X[j]-X[w])< s[i]
这题解决了。(不过要用滚动数组,卡空间)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
int now=1;long long n,m,s[110000],f[2][110000],v[110000],a[110000];
long long YY(int k,int x){return s[x]*s[x]-f[1-k][x];}
long long Y(int k,int x,int y){return YY(k,y)-YY(k,x);}
long long X(int x,int y){return s[y]-s[x];}
int main()
{ 
    int t=0;
    scanf("%lld%lld",&n,&m);
    m=min(m,n-1);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++){if(a[i]!=0)a[++t]=a[i];}
    n=t;
    for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
    memset(f,0,sizeof(f));
    for(int j=1;j<=m;j++)
    {
        int head=1,tail=1;now=1-now;
        for(int i=j;i<=n;i++)
        {
            while(head<tail && Y(now,v[head],v[head+1])<s[i]*X(v[head],v[head+1]))head++;
            int x=v[head];
            f[now][i]=f[1-now][x]+(s[i]-s[x])*s[x];
            while(head<tail && Y(now,v[tail-1],v[tail])*X(v[tail],i)>Y(now,v[tail],i)*X(v[tail-1],v[tail]))tail--;
            v[++tail]=i;
        }
    }
    printf("%lld\n",f[now][n]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值