前言:
自从发现1D/1D动态规划这个新的技能块后,每次看到这类题目都能口嗨成功,然后队友催我赶紧学。再者发现有好多优化方法,所以慢慢学,每天学一点,加上还要做一些奇奇怪怪的题目保持状态,也就差不多了。
第0天(1D/1D动态规划)
什么是1D/1D动态规划?
1D/1D动态规划是指形如
d
p
i
=
m
a
x
{
d
p
j
+
w
(
i
,
j
)
}
dp_{i}=max\{ dp_{j}+w(i,j)\}
dpi=max{dpj+w(i,j)}的动态规划,当然
m
a
x
max
max也可以换成
m
i
n
min
min。
据说长这样的
d
p
dp
dp一般都可以优化到
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)。
优化的思路就是在
j
j
j的范围中,快速找到一个最优的转移点。
学习思路大概就是学习这个转移方程的不同类型,然后对于每种类型学习它的优化方法。
学习链接(多去走走,这样笔者偷完知识就不会愧疚了,博客里会加入自己的东西(也许))
第0.5天(前缀和优化)
有个前缀和优化,这个笔者默认会了,就不学了。不会的话请读者自行学习。
第1天(单调队列优化)
啊~新的力量!
今天是单调队列优化dp,当然,就当大家都会单调队列了。
实话说,学完感觉原来的
d
p
dp
dp式也有点不恰当,我们把
d
p
j
dp_{j}
dpj也看作是
w
(
i
,
j
)
w(i,j)
w(i,j)中
j
j
j的一部分运算,那式子其实就是
d
p
i
=
m
a
x
{
w
(
i
,
j
)
}
dp_{i}=max\{ w(i,j)\}
dpi=max{w(i,j)}。
好了,进入今天的正题。
按照我们的学习思路,我们应该对于此类优化,有一个归纳的转移式子,那就有了。
转移方程
d p i = m a x { w ( j ) } dp_{i}=max\{ w(j)\} dpi=max{w(j)}
说明
这个方程的意思就是 d p dp dp的转移只和 j j j有关。然后这个 j j j呢,一般是一个区间范围,并且随着 i i i的变大, j j j的范围区间是整体右移的。这样可以保证它能在转移过程中,用单调队列找到最优点。
思路:
说明中提到 j j j的范围区间整体右移,我们设转移 d p i − 1 dp_{i-1} dpi−1时 j j j的范围区间是 [ l i − 1 , r i − 1 ] [l_{i-1},r_{i-1}] [li−1,ri−1],转移 d p i dp_{i} dpi时 j j j的范围区间是 [ l i , r i ] [l_{i},r_{i}] [li,ri]。那么我们 r r r变大的时候,就把新的元素加入到单调队列右边(如果要加的话,在这之前还要把右边一些不要的元素拿走),然后答案在队列左边找到范围内的。当然,这都是单调队列的东西,就不细讲了。
实战例题
题意
n
n
n棵树,每棵树有一个高度
d
i
d_{i}
di,小
H
H
H在第
1
1
1棵树上,要跳到第
n
n
n棵树上。在第
i
i
i棵树上时,可以跳到第
i
,
i
+
1
,
…
,
i
+
k
i,i+1,…,i+k
i,i+1,…,i+k棵树上。每跳一次,如果高度不小于原来的高度,疲劳值就会加
1
1
1。现在问小
H
H
H从第
1
1
1棵树上跳到第
n
n
n棵树上,最少的疲劳值是多少。
(
1
≤
n
≤
k
≤
1
0
6
)
(1\leq n\leq k\leq 10^{6})
(1≤n≤k≤106)
思路
我们设状态
d
p
i
dp_{i}
dpi表示跳到第
i
i
i棵树上需要花费的最少疲劳值。
那么有转移方程
d
p
i
=
m
i
n
{
d
p
j
+
[
d
i
>
=
d
j
]
}
dp_{i}=min\{dp_{j}+[d_{i}>=d_{j}]\}
dpi=min{dpj+[di>=dj]},其中
j
∈
[
i
−
k
,
i
−
1
]
j\in [i-k,i-1]
j∈[i−k,i−1]。
我们要学习这个思路。
首先我们观察
j
∈
[
i
−
k
,
i
−
1
]
j\in [i-k,i-1]
j∈[i−k,i−1],我们发现区间随着
i
i
i变大整体右移,成立。
然后
d
p
j
+
[
d
i
>
d
j
]
dp_{j}+[d_{i}>d_{j}]
dpj+[di>dj],它是一个关于
i
i
i和
j
j
j的式子,呃好像不成立。
这时候只能发挥我们的聪明才智了,咳咳。
我们先发现
d
p
i
dp_{i}
dpi单调不下降,且每次增加都是加
1
1
1。所以对于两个
d
p
j
dp_{j}
dpj的值,我们肯定直接取小的那个,因为小的那个
+
1
+1
+1也不会超过大的。然后对于两个相同的
d
p
j
dp_{j}
dpj,我们一定优先取
d
j
d_{j}
dj大的那个,也就是要在范围内找使得
(
−
d
p
j
,
d
j
)
(-dp_{j},d_{j})
(−dpj,dj)最大的
j
j
j。这样就可以用单调队列优化了。
刚才演示翻车了,我决定用 d p i = w ( i , j ) , w h e r e g ( j ) i s m a x f o r j i n r a n g e dp_{i}=w(i,j),where\;g(j)\;is\;max\;for\;j\;in\;range dpi=w(i,j),whereg(j)ismaxforjinrange来作为新式子。
这样也许就可以泛化模型了。
代码
题目有点卡,开o2优化就过了。
写完下班。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
int q[1000005],fr,bk;
int d[1000005];
int dp[1000005];
int w(int i,int j)
{
return dp[j]+(d[i]>=d[j]);
}
P g(int j)
{
return P(-dp[j],d[j]);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&d[i]);
int m;
scanf("%d",&m);
while(m--)
{
fr=bk=0;
int k;
scanf("%d",&k);
dp[1]=0;
q[bk++]=1;
for(int i=2;i<=n;i++)
{
while(q[fr]<i-k)fr++;
dp[i]=w(i,q[fr]);
while(!(fr==bk)&&g(q[bk-1])<=g(i))bk--;
q[bk++]=i;
}
printf("%d\n",dp[n]);
}
return 0;
}
第二天(分治优化)
(待续。。)
(upd:更新了标题后面不应该加冒号的bug)
昨天是关于
j
j
j独立的转移,依据
j
j
j的单调性用单调队列优化。
那么今天这个是不关于
j
j
j独立,即和
i
i
i和
j
j
j都有关的,刚学了一种分治优化,当然你得先会分治。
转移方程
d
p
i
=
m
a
x
(
w
(
i
,
j
)
)
,
j
∈
[
1
,
i
−
1
]
dp_{i}=max(w(i,j)),j \in [1,i-1]
dpi=max(w(i,j)),j∈[1,i−1]
又或者说
d
p
i
=
w
(
i
,
j
)
,
w
h
e
r
e
g
(
i
,
j
)
i
s
m
a
x
f
o
r
j
i
n
r
a
n
g
e
dp_{i}=w(i,j),where\;g(i,j)\;is\;max\;for\;j\;in\;range
dpi=w(i,j),whereg(i,j)ismaxforjinrange
并且还需要它的转移,随着
i
i
i满足某种决策单调性。
说明
总之,我们需要找的最优转移点和
i
,
j
i,j
i,j都有关。
然后,转移要随着
i
i
i满足决策单调性。举个例子,大致意思是,当
i
i
i 慢慢变大,最优决策点
j
j
j也会慢慢变大。但这不意味着
j
j
j大更优,有些
j
j
j压根不会被作为最优决策点,只是哪些对每个
i
i
i作为最优决策点的
j
j
j们是随着
i
i
i变大而变大的(不下降)。
思路
分治的大致做法是,我们先找中间的
d
p
m
i
d
dp_{mid}
dpmid的转移点
q
m
i
d
qmid
qmid。然后根据决策单调性,当
i
<
m
i
d
i<mid
i<mid时,转移点
j
∈
[
1
,
q
m
i
d
]
j\in[1,qmid]
j∈[1,qmid];当
i
>
m
i
d
i>mid
i>mid时,转移点
j
∈
[
q
m
i
d
,
n
]
j\in[qmid,n]
j∈[qmid,n]且
j
<
i
j<i
j<i。
然后每次这样取中点求最优转移点,把区间割成两个一半。递归深度是
l
o
g
log
log级的,那我们找转移点就暴力遍历可能存在最优转移点的决策区间。总体复杂度
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)。
实战例题
题意
给定一个长度为
n
n
n的序列
{
a
n
}
\{a_{n}\}
{an},对于每个
i
∈
[
1
,
n
]
i\in [1,n]
i∈[1,n],求出一个最小的非负整数
p
p
p,使得
∀
j
∈
[
1
,
n
]
\forall j\in[1,n]
∀j∈[1,n],都有
a
j
≤
a
i
+
p
−
∣
i
−
j
∣
a_j\le a_i+p-\sqrt{|i-j|}
aj≤ai+p−∣i−j∣
1
≤
n
≤
5
×
1
0
5
,
0
≤
a
i
≤
1
0
9
1\leq n\leq 5\times 10^{5},0\leq a_{i}\leq 10^{9}
1≤n≤5×105,0≤ai≤109
思路
我们令
d
p
i
dp_{i}
dpi表示第
i
i
i个答案。
首先,把绝对值处理掉,我们可以正向跑一遍
d
p
dp
dp,再逆向跑一遍,求个最大值。
那么正向的转移方程就是
d
p
i
=
m
a
x
{
a
j
−
a
i
+
i
−
j
}
,
j
∈
[
1
,
i
−
1
]
dp_{i}=max\{a_{j}-a_{i}+\sqrt{i-j}\},j\in [1,i-1]
dpi=max{aj−ai+i−j},j∈[1,i−1]。
关于
i
i
i常量先提出来,变成
d
p
i
=
m
a
x
{
a
j
+
i
−
j
}
−
a
i
,
j
∈
[
1
,
i
−
1
]
dp_{i}=max\{a_{j}+\sqrt{i-j}\}-a_{i},j\in [1,i-1]
dpi=max{aj+i−j}−ai,j∈[1,i−1]。
那么现在变成了,我们所说的模范转移式
d
p
i
=
m
a
x
{
w
(
i
,
j
)
}
dp_{i}=max\{w(i,j)\}
dpi=max{w(i,j)},其中
w
(
i
,
j
)
=
a
i
+
i
−
j
w(i,j)=a_{i}+\sqrt{i-j}
w(i,j)=ai+i−j。
然后,它要满足我们所说的决策单调性,即
i
i
i变大的时候,我们选取的最优决策点
j
j
j也会慢慢变大。我们观察式子
w
j
(
i
)
=
a
i
+
i
−
j
w_{j}(i)=a_{i}+\sqrt{i-j}
wj(i)=ai+i−j是一个关于
i
i
i的函数,有
i
−
1
i-1
i−1个这样的函数,我们要对于每个
i
i
i
选取一个最佳的函数
w
j
w_{j}
wj,使得函数值最高。
然后我们发现这些函数的增长速度都是慢慢变慢的,且
w
j
w_{j}
wj比
w
j
+
1
w_{j+1}
wj+1在
i
i
i相同时增长速度要慢。也就是一旦
i
i
i增长到某个时候
w
j
w_{j}
wj被一个
w
k
w_{k}
wk超过了,且
k
>
j
k>j
k>j,那么
w
j
w_{j}
wj将永无翻身之日。
总的来说,就是满足当
i
i
i变大,最优决策点只会不断变大(或者换个题变小)。
满足我们前面所说的性质,然后就可以分治搞了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
double a[500005];
ll dp1[500005],dp2[500005];
double w(int i,int j)
{
return a[j]+sqrt(abs(i-j))-a[i];
}
void solve(int l,int r,int ql,int qr,ll *dp)
{
if(l>r||ql>qr)return ;
int mid=(l+r)/2;
int qmid=ql;
for(int i=ql;i<=qr&&i<mid;i++)if(w(mid,i)>w(mid,qmid))qmid=i;
dp[mid]=ceil(w(mid,qmid));
solve(l,mid-1,ql,qmid,dp);solve(mid+1,r,qmid,qr,dp);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lf",&a[i]);
solve(1,n,1,n,dp1);
reverse(a+1,a+n+1);
solve(1,n,1,n,dp2);
reverse(dp2+1,dp2+n+1);
for(int i=1;i<=n;i++)printf("%lld\n",max(0ll,max(dp1[i],dp2[i])));
return 0;
}
第N天
(待续。。)
拿金了,先断更了