【BZOJ3675】序列统计,斜率优化DP

传送门
写在前面:停课的开始
思路:
这是一道有点丧病的斜率DP。
首先发现,如果要切位置i和j,那么先切i和先切j得到的是同样的结果(只是想着应该是这样,用暴力试了一下,但太弱不会证明)
我的DP方程好像和网上的不太一样
f[i][j]是指在第j个数的前面”砍“第i下的最大值(即把第j个数当作第i段的末尾)那么转移方程就显而易见了
f[i][j]=max(f[i1][p]+(sum[n]sum[j])(sum[j]sum[p]))p=0..j
最终答案是 max(f[k][i])i=1..n1
( sum[x]=xi=1a[i] )
设x>y且x优于y的转移,则
(f[x]f[y])/(sum[x]sum[y])>sum[n]sum[j]
而且计算100000*200*8/1024/1024≈152,空间爆炸,所以开滚动数组,队列记录上一次的转移,head和tail也要做些小处理,答案用long long计算
然后就过了样例,欢天喜地地交上去了
不出4ms就WA了……
又试了一组数据,发现和暴力结果不同
os:好像数据中0比较多?
结果看了眼std发现要特判0?
os:???
想了好久发现判断斜率时的 sum[x]sum[y] 可能会因为a[x]=0而除0出错(除以实数0会爆出一些奇怪的东西(‵~′))……
所以要判0啊!判0啊!
附上文所说的证明(转自DaD3zZ):

大体上假设某串为abcdabcd,如果最后要分割成a|b|cda|b|cd那么
先分割成ab|cdab|cd当前答案为a∗cd+b∗cda∗cd+b∗cd,再分割成a|b|cda|b|cd,答案为a∗b+a∗cd+b∗cda∗b+a∗cd+b∗cd
先分割成a|bcda|bcd当前答案为a∗bcda∗bcd,在分割成a|b|cda|b|cd,答案为a∗bcd+b∗cda∗bcd+b∗cd
那么两式化一化就可以发现得到的是相同的。所以,对于其余的也合适;

注意:判0啊!
代码:

#include<bits/stdc++.h>
#define M 100002
#define LL long long
using namespace std;
int n,k,a[M],head[2],tail[2],q[2][M];
LL sum[M],f[2][M],ans;
bool mk;
double Get(bool i,int x,int y)
{
    if (sum[x]==sum[y]) return 0;   
    return (double)(f[i][x]-f[i][y])/(double)(sum[x]-sum[y]);
}
main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++)
        scanf("%d",a+i),
        sum[i]=sum[i-1]+a[i];
    head[0]=tail[0]=1;
    for (int i=1;i<=k;i++)
    {
        mk^=1;
        head[mk]=tail[mk]=1;
        for (int j=1;j<n;j++)
        {
            while (head[!mk]<tail[!mk]&&Get(!mk,q[!mk][head[!mk]+1],q[!mk][head[!mk]])>sum[n]-sum[j]) head[!mk]++;
            f[mk][j]=f[!mk][q[!mk][head[!mk]]]+(sum[n]-sum[j])*(sum[j]-sum[q[!mk][head[!mk]]]);
            while (head[mk]<tail[mk]&&Get(mk,j,q[mk][tail[mk]])>Get(mk,q[mk][tail[mk]],q[mk][tail[mk]-1])) tail[mk]--;
            q[mk][++tail[mk]]=j;
        }
    }
    for (int i=1;i<n;i++)    ans=max(ans,f[mk][i]);
    printf("%lld",ans);
}

冷静下来,重新看看题目,其实和之前的斜率DP没什么两样,但也许是觉得太模式化了,连最基本的除0错误都忽略掉了,不得不说是对于细节的把握力不够而导致的,也许再细心一些,就能在不通过外力的情况下找出这种小错误了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值