斜率优化dp入门-学习笔记
最近在学斜率优化。
但是讲课的学长默认我们会这个东西,于是其它部分都听得懂一到斜率优化就开始一片空白 。
感觉跟单调队列优化dp很像,但要复杂一些。
感觉 就是一直在看决策点哪个更优什么的然后不停地化简移项然后化简成不等式的一边有类似斜率那样的一个形式,然后再根据题目的一些什么性质进行维护。
最近实在是有太多事情要干了,感觉学东西好快呀,这既是好事又不好。(怎么又开始哲学)
所以还是粘一下参考的博客,自己就不详细解释了。
https://www.cnblogs.com/orzzz/p/7885971.html
https://blog.csdn.net/lxc779760807/article/details/51366552
https://www.cnblogs.com/kuangbin/archive/2012/08/26/2657650.html
入门做的题目就是HDU的3507
(我就说这才是板题,学长非要把他放在后面,果然学长太大佬了看什么题都是板题)
题意:
给出N(1e4)个单词,每个单词有个非负权值Ci,
现要将它们分成连续的若干段,每段的代价为此段单词的权值和的平方,
还要加一个常数M,即(∑Ci)^2+M。
现在想求出一种最优方案,使得总费用之和最小。
大概理一下思路吧 (其实我觉得大佬们的博客都已经讲得很清楚了诶)
1.首先写出转移式:
d
p
[
i
]
=
m
i
n
(
d
p
[
j
]
+
(
s
[
i
]
−
s
[
j
]
)
2
+
M
)
dp[i]=min(dp[j]+(s[i]-s[j])^2+M)
dp[i]=min(dp[j]+(s[i]−s[j])2+M)
d
p
[
i
]
dp[i]
dp[i]表示输出前
i
i
i个单词(包括
i
i
i)的最小费用
2.套路:假设
j
>
k
j>k
j>k且
j
j
j比
k
k
k更优,那么有:
d
p
[
j
]
+
(
s
[
i
]
−
s
[
j
]
)
2
+
M
<
d
p
[
k
]
+
(
s
[
i
]
−
s
[
k
]
)
2
+
M
dp[j]+(s[i]-s[j])^2+M<dp[k]+(s[i]-s[k])^2+M
dp[j]+(s[i]−s[j])2+M<dp[k]+(s[i]−s[k])2+M
化至套路格式:
(
d
p
[
j
]
+
s
[
j
]
2
)
−
(
d
p
[
k
]
+
s
[
k
]
2
)
2
(
s
[
j
]
−
s
[
k
]
)
<
s
[
i
]
\frac{(dp[j]+s[j]^2)-(dp[k]+s[k]^2)}{2(s[j]-s[k])}<s[i]
2(s[j]−s[k])(dp[j]+s[j]2)−(dp[k]+s[k]2)<s[i]
设
d
p
[
j
]
+
s
[
j
]
2
dp[j]+s[j]^2
dp[j]+s[j]2为
y
j
y_{j}
yj,
2
∗
s
[
j
]
2*s[j]
2∗s[j]为
x
j
x_{j}
xj,继续套路:
y
j
−
y
k
x
j
−
x
k
<
s
[
i
]
\frac{y_{j}-y_{k}}{x_{j}-x_{k}}<s[i]
xj−xkyj−yk<s[i]
设
g
(
j
,
k
)
=
y
j
−
y
k
x
j
−
x
k
g(j,k)=\frac{y_{j}-y_{k}}{x_{j}-x_{k}}
g(j,k)=xj−xkyj−yk,即当满足条件:
j
>
k
j>k
j>k
g
(
j
,
k
)
<
s
[
i
]
g(j,k)<s[i]
g(j,k)<s[i]
时,
j
j
j比
k
k
k更优。
反之,若
j
>
k
j>k
j>k,
g
(
j
,
k
)
>
=
s
[
i
]
g(j,k)>=s[i]
g(j,k)>=s[i]时,
k
k
k比
j
j
j更优。
3.若
i
>
j
>
k
i>j>k
i>j>k,
g
(
i
,
j
)
<
g
(
j
,
k
)
g(i,j)<g(j,k)
g(i,j)<g(j,k),则可以淘汰
j
j
j,证明大致如下:
分三种情况讨论:
1.
g
(
i
,
j
)
<
g
(
j
,
k
)
<
s
[
i
]
g(i,j)<g(j,k)<s[i]
g(i,j)<g(j,k)<s[i],
i
i
i比
j
j
j优,
j
j
j比
k
k
k优
2.
g
(
i
,
j
)
<
s
[
i
]
<
g
(
j
,
k
)
g(i,j)<s[i]<g(j,k)
g(i,j)<s[i]<g(j,k),
i
i
i比
j
j
j优,
k
k
k比
j
j
j优
3.
s
[
i
]
<
g
(
i
,
j
)
<
g
(
j
,
k
)
s[i]<g(i,j)<g(j,k)
s[i]<g(i,j)<g(j,k),
j
j
j比
i
i
i优,
k
k
k比
j
j
j优
无论如何,
j
j
j都不会是最优的,可以把
j
j
j去掉。
所以,斜率一定是单调递增的,可以用单调队列维护。
(
g
(
i
,
j
)
<
g
(
j
,
k
)
g(i,j)<g(j,k)
g(i,j)<g(j,k),也即是
(
y
i
−
y
j
)
(
x
j
−
x
k
)
<
(
x
i
−
x
j
)
(
y
j
−
y
k
)
(y_i-y_j)(x_j-x_k)<(x_i-x_j)(y_j-y_k)
(yi−yj)(xj−xk)<(xi−xj)(yj−yk))
实际操作中,这里可以取等号,为方便起见,上面的证明没有给出边界(恰好取等)的状态。
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 500005
int n,m;
int dp[MAXN],s[MAXN];
int Q[MAXN];
int fz(int j,int k)
{
return dp[j]+s[j]*s[j]-(dp[k]+s[k]*s[k]);
}
int fm(int j,int k)
{
return 2*(s[j]-s[k]);
}
int main()
{
while(scanf("%d %d",&n,&m)!=EOF)
{
s[0]=dp[0]=0;
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
s[i]=s[i-1]+x;
}
int head=1,tail=1;
Q[1]=0;//把0放进去 表示从1到i就是一整段 不分
//Q[1]=1;dp[1]=s[1]*s[1]+m;这么写会WA 原理见上一行
for(int i=1;i<=n;i++)
{//s[i]单调递增 斜率也单调递增
while(head<tail/*至少有2个数*/&&fz(Q[head+1],Q[head])<=s[i]*fm(Q[head+1],Q[head]))
head++;
int j=Q[head];
dp[i]=dp[j]+(s[i]-s[j])*(s[i]-s[j])+m;
while(head<tail&&fz(i,Q[tail])*fm(Q[tail],Q[tail-1])<=fm(i,Q[tail])*fz(Q[tail],Q[tail-1]))
tail--;
Q[++tail]=i;
}
printf("%d\n",dp[n]);
}
return 0;
}