树形DP&环形和后效性处理
树形dp
概念
依然是 O ( n ) O(n) O(n)类型的dp,通过dfs或bfs实现小状态向大状态的转移,本质是在树上更方便表示的线性动规
例题
P2014 [CTSC1997]选课
是做过的题,就不详细说了
树上的分组背包
POJ3585
从树上选取一个源点作为根,每条边都有一个容量,该树对应的叶子结点为汇点
问应当选取哪个点为源点,该树的流量最大
朴素算法:
枚举每个点作为源点,用树形dp求出最大流量
也就是从下往上取min
d[x]
表示以
x
x
x为根的子树所得的最大流量
d
[
x
]
=
∑
y
∈
s
o
n
(
x
)
{
m
i
n
(
d
[
y
]
,
c
(
x
,
y
)
)
(
y
的
度
数
>
1
)
c
(
x
,
y
)
(
y
的
度
数
为
1
)
d[x]=\sum_{y\in son(x)}\begin{cases} min(d[y],c(x,y))(y的度数>1)\\ c(x,y)(y的度数为1)\\ \end{cases}
d[x]=y∈son(x)∑{min(d[y],c(x,y))(y的度数>1)c(x,y)(y的度数为1)
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+7;
vector<int> son[N];
int d[N],deg[N];
bool vis[N];
int n,T;
void dp(int x){
d[x]=0,v[x]=1;
for(int i=0;i<son[x].size();i++){
int y=son[x][i];
if(vis[y]) continue;
dp(y);
if(deg[y]==1) d[x]+=val[x][i];
else d[x]+=min(d[x],d[y]+val[x][i]);
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
memset(d,0,sizeof(d));
memset(vis,0,sizeof(vis));
memset(deg,0,sizeof(deg));
//...
}
return 0;
}
是否存在冗余呢?
答案是肯定的,因为每条边只有正向反向两种情况
本质只有 2 n 2n 2n种情况
二次扫描&换根法
我们首先以
x
x
x为根得到d[i]
,求以y为根的结果f[i]
我们会得到:
f
[
y
]
=
d
[
y
]
+
{
m
i
n
(
f
[
x
]
−
m
i
n
(
d
[
y
]
,
c
(
x
,
y
)
)
,
c
(
x
,
y
)
)
(
d
e
g
[
x
]
>
1
,
d
e
g
[
y
]
>
1
)
m
i
n
(
f
[
x
]
−
c
(
x
,
y
)
,
c
(
x
,
y
)
)
(
d
e
g
[
x
]
>
1
,
d
e
g
[
y
]
=
1
)
c
(
x
,
y
)
(
d
e
g
[
x
]
=
1
)
f[y]=d[y]+\begin{cases} min(f[x]-min(d[y],c(x,y)),c(x,y))\ \ \ \ \ \ \ \ (deg[x]>1,deg[y]>1)\\ min(f[x]-c(x,y),c(x,y))\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (deg[x]>1,deg[y]=1)\\ c(x,y)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (deg[x]=1)\\ \end{cases}
f[y]=d[y]+⎩⎪⎨⎪⎧min(f[x]−min(d[y],c(x,y)),c(x,y)) (deg[x]>1,deg[y]>1)min(f[x]−c(x,y),c(x,y)) (deg[x]>1,deg[y]=1)c(x,y) (deg[x]=1)
复杂度
O
(
n
)
O(n)
O(n)
环形dp
概念
对于可拆解的环,我们有 O ( n ) O(n) O(n)做法枚举断点
我们的目的是更加高效的处理这一类问题
例题
Acwing288. 休息时间
我们一天有
n
n
n个小时,可以选择
b
b
b个小时休息,其中每段休息的第一个小时为入睡时间,每个睡着的小时可以活的u[i]
点体力
朴素算法:
f[i][j][0/1]
表示前
i
i
i小时选
j
j
j小时休息,其中第
i
i
i小时是否休息所获得的最大体力
f [ i ] [ j ] [ 0 ] = m a x ( f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j ] [ 1 ] ) f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]) f[i][j][0]=max(f[i−1][j][0],f[i−1][j][1])
f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j − 1 ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + u [ i ] ) f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+u[i]) f[i][j][1]=max(f[i−1][j−1][0],f[i−1][j−1][1]+u[i])
初值: f [ 1 , 0 , 0 ] = f [ 0 , 0 , 0 ] = 0 , f [ 1 ] [ 1 ] [ 1 ] = f [ 0 ] [ 0 ] [ 0 ] = 0 f[1,0,0]=f[0,0,0]=0,f[1][1][1]=f[0][0][0]=0 f[1,0,0]=f[0,0,0]=0,f[1][1][1]=f[0][0][0]=0
其中 m a x ( f [ n ] [ b ] [ 0 ] , f [ n ] [ b ] [ 1 ] ) max(f[n][b][0],f[n][b][1]) max(f[n][b][0],f[n][b][1])即为所求
这样我们便处理了一个序列上的问题
不难发现我们漏掉了u[1]
的收益
总
状
态
=
{
第
1
小
时
没
睡
着
第
1
小
时
睡
着
总状态=\begin{cases} 第1小时没睡着\\ 第1小时睡着\\ \end{cases}
总状态={第1小时没睡着第1小时睡着
对于没考虑到的情况,我们令
f
[
1
]
[
1
]
[
1
]
=
u
[
1
]
f[1][1][1]=u[1]
f[1][1][1]=u[1]
进行dp,最后目标为 f [ n ] [ b ] [ 1 ] f[n][b][1] f[n][b][1]
两种情况互补,我们取max即可
复杂度 O ( n 2 ) O(n^2) O(n2)
本题中 1 1 1与 n n n之间存在限制条件
常用的方法为分类讨论,将该限制条件拆分为满足和不满足,求两次dp
Acwing289. 环路运输
环形公路上有 n n n个仓库,第 i i i个存有 a i a_i ai个货物
我们定义 d i s t ( i , j ) = m i n ( ∣ i − j ∣ , N − ∣ i − j ∣ ) dist(i,j)=min(|i-j|,N-|i-j|) dist(i,j)=min(∣i−j∣,N−∣i−j∣)
求那两座城市运输货物的代价最大
我们把 1 1 1~ n n n的序列复制一倍,其中长度为 n n n的一段即为一种情况
a n s = a [ i ] + i + m a x i − n / 2 ≤ j < i { a [ j ] − j } ans=a[i]+i+max_{i-n/2\leq j<i} \{a[j]-j \} ans=a[i]+i+maxi−n/2≤j<i{a[j]−j}
max内的部分用单调队列维护
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;
int a[N],q[N];
int n,ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=1,r=1;
q[1]=0;
for(int i=1;i<=2*n;i++){
while(l<=r&&q[l]<i-n/2) l++;
ans=max(ans,a[i]+i+a[q[l]]-q[l]);
while(l<=r&&a[i]-i<=a[q[r]]-q[r]) r--;
q[++r]=i;
}
printf("%d\n",ans);
}
后效性处理
处理方法
高斯消元法: 我们把每个状态看做未知数,每个转移看做方程,运用高斯消元求解
SPFA: dp后效性的本质在于图上有环,SPFA可以多次遍历某个点
例题
Acwing290. 坏掉的机器人
给定一张 N × M N×M N×M 的棋盘,有一个机器人处于 ( x , y ) (x,y) (x,y) 位置
这个机器人可以进行很多轮行动,每次等概率地随机选择停在原地,向左移动一格,向右移动一格或向下移动一格
当然机器人不能移出棋盘
求机器人从起点走到最后一行的任意一个位置上,所需行动次数的数学期望值
f [ i ] [ j ] = 1 4 ( f [ i ] [ j ] + f [ i ] [ j − 1 ] + f [ i ] [ j + 1 ] + f [ i + 1 ] [ j ] ) + 1 f[i][j]=\frac{1}{4}(f[i][j]+f[i][j-1]+f[i][j+1]+f[i+1][j])+1 f[i][j]=41(f[i][j]+f[i][j−1]+f[i][j+1]+f[i+1][j])+1
我们得到一个 m × m m\times m m×m的矩阵进行高斯消元
复杂度 O ( n × m 3 ) O(n\times m^3) O(n×m3)