树形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 ]