世界真的很大
上周学了斜率优化dp,惭愧昨天才搞懂,花了一天整理了一下思绪总结如下:
许多朴树的dp转移方程可以写成 f[i]=f[j]+w[i] 的格式,这就意味着i的决策仅仅取决于j的决策优劣,可以由单调队列把n^2优化到n,这里不赘述。
但更多的转移方程并不能化简成如下格式,比如这道hdu3507:
先叙述下题意,给出一个数组,将其分为几组,每一组算出其和的平方加上给定数m,将每一组的值求和然后输出,求最小值。
提意明确,列出状态转移方程:
f[i]=f[j]+m+(sum[i]-sum[j])^2
注意,这个方程并不能用单调队列解决,因为i的决策不仅取决于j,还取决于(sum[i]-sum[j])^2,这就无法对f进行单调队列保存。
对方程进行数学处理,假定j和k,在决策i时,j比k优:
dp[j]+m+(sum[i]-sum[j])^2<dp[k]+m+(sum[i]-sum[k])^2
直接打开消元得出:
dp[j]+sum[j]^2-2*sum[i]*sum[j]< dp[k]+sum[k]^2-2*sum[i]*sum[k]
移项相除得:
dp[j]+sum[j]^2-(dp[k]+sum[k]^2)/2*(sum[j]-sum[k])<sum[i]
仔细一看的话,左边就是一个只关于j,k的斜率表达式了。定义左边为g(j,k)<sum[i]的话,说明在决策i时,j比k优,那么可以单调队列来维护斜率,最小的斜率最优且在对首。
为什么可以这样呢?再解释一遍。
因为我们发现,在比较决策i时的前驱决策j,k谁优时,得到了一个关乎斜率的等式g(j,k),g(j,k)<sum[i]时,说明j比k优,就是说,凡是斜率比g(j,k)小,那一定没有sum[i]大,如g(a,b)<g(b,c)<g(c,d)….<g(j,k)<sum[i],那就是a没有b优,b没有c优。。。如果用单调斜率的队列,就可以瞬间排除j以前的选项,排除选项,即为优化。
现在我们的单调队列维护的就是斜率的单调递增了,在决策i时,凡是对首斜率比sum[i]小的,都可以直接弹出去,因为不优,如上解释,每次队首一定是最优的,因为后面的都大于sum[i]了,不满足g(h,h+1)
bool check_h(int i)
{
return y(state[h+1],state[h])<=sum[i]*x(state[h+1],state[h]);
}
队尾的话直接按graham-scan(古来哈姆–斯侃胡)的方法维护下凸的凸包就行了,但注意的是,我们维护的不是整个凸包而是零碎的小节,代码:
bool check_t(int i)
{
return y(i,state[t])*x(state[t],state[t-1])<=y(state[t],state[t-1])*x(i,state[t]);
}
x和y分别是之前数学证明里的分子和分母,因为太长了就写成了函数,如下:
int x(int u,int v)
{
return 2*(sum[u]-sum[v]);
}
int y(int u,int v)
{
return f[u]+sum[u]*sum[u]-(f[v]+sum[v]*sum[v]);
}
完整代码如下:
#include<stdio.h>
int sum[500050],f[500050],h,t,n,m,state[500050];
int x(int u,int v)
{
return 2*(sum[u]-sum[v]);
}
int y(int u,int v)
{
return f[u]+sum[u]*sum[u]-(f[v]+sum[v]*sum[v]);
}
bool check_h(int i)
{
return y(state[h+1],state[h])<=sum[i]*x(state[h+1],state[h]);
}
bool check_t(int i)
{
return y(i,state[t])*x(state[t],state[t-1])<=y(state[t],state[t-1])*x(i,state[t]);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
}
h=1,t=1,state[1]=0;
for(int i=1;i<=n;i++)
{
while(h<t&&check_h(i)) h++;
f[i]=f[state[h]]+m+(sum[i]-sum[state[h]])*(sum[i]-sum[state[h]]);
while(h<t&&check_t(i)) t--;
state[++t]=i;
}
printf("%d\n",f[n]);
}
return 0;
}
恩,就是这样;