您的位置:首页 > 其它

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

2016-04-22 21:05 274 查看
传送门

写在前面:停课的开始

思路:

这是一道有点丧病的斜率DP。

首先发现,如果要切位置i和j,那么先切i和先切j得到的是同样的结果(只是想着应该是这样,用暴力试了一下,但太弱不会证明)

我的DP方程好像和网上的不太一样

f[i][j]是指在第j个数的前面”砍“第i下的最大值(即把第j个数当作第i段的末尾)那么转移方程就显而易见了

f[i][j]=max(f[i−1][p]+(sum[n]−sum[j])∗(sum[j]−sum[p]))p=0..j

最终答案是max(f[k][i])其中i=1..n−1

(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
-sum[j]) head[!mk]++;
f[mk][j]=f[!mk][q[!mk][head[!mk]]]+(sum
-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错误都忽略掉了,不得不说是对于细节的把握力不够而导致的,也许再细心一些,就能在不通过外力的情况下找出这种小错误了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: