【笔记】【树形dp】
1>树的直径
【树形dp】
(图来自luogu)
从图中可以得知,以任意一个点为根,树的直径必然有一个dep最小的点,
直径是这个点子树中,两个最长链的和,(如果只有一个链,另一个当做0就好)
这里可以用贪心证得
没有了后效性,就可以 叶子->根 ,dp转移状态了
#include<cstdio> #include<cstdlib> #include<vector> using namespace std; int n,ans; const int N=10003; vector <int> g[N]; int d1[N],d2[N]; void dfs(int rt,int f) { int sz=g[rt].size() ; for(int i=0;i<sz;i++) { int nx=g[rt][i]; if(nx==f) continue; dfs(nx,rt); if(d1[nx]+1>d1[rt]) { d2[rt]=d1[rt]; d1[rt]=d1[nx]+1; } else if(d1[nx]+1>d2[rt]) d2[rt]=d1[nx]+1; ans=max(d1[rt]+d2[rt],ans); } } int main() { int u,v; scanf("%d",&n); for(int i=1;i<n;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u); dfs(1,0); printf("%d\n",ans); return 0; }
【dfs x 2】
(来自luogu)
做两遍dfs,第一遍从任意一个点开始,找到离它最远的点。第二遍从第一遍找到的点开始,再找离他最远的点,这条路径就是树的直径。
证明:
设A和B是直径的两个端点,C是第一次dfsdfs的起点,D是终点。若D不是A或B,则一定有一条更长的链。但这和上面说的“设A和B是直径的两个端点”是矛盾的。
#include<bits/stdc++.h> using namespace std; int n,ll,ss=1,ans; vector<int>g[10005];//动态数组,不会的看底下的链接 bool f[100005];//记录该节点是否来过 inline void dfs(int l,int s,bool flag)//flag只是为了不写两个函数,因为两次dfs都是大同小异 { bool hh=false;//hh用来判断该节点能否向外延伸,如果不能就是搜到了终点 for(register int i=0;i<g[s].size();i++) if(!f[g[s][i]])//如果没有来过 { hh=true;//表示可以向外延伸 f[g[s][i]]=true;//标记为走过 dfs(l+1,g[s][i],flag);//继续dfs } if(!hh)//如果不能延伸 { if(flag) ans=max(ans,l);//如果是要求直径长度,即第二次dfs else if(l>ll) ll=l,ss=s;//第一次要记录长度,用来判断,记录端点,给第二次dfs做起点 } return ; } int main() { scanf("%d",&n); for(register int i=1;i<n;i++) { int x,y; cin>>x>>y; g[x].push_back(y); g[y].push_back(x); } dfs(0,1,0);//第一次dfs memset(f,0,sizeof(f));//记得初始化!!! dfs(0,ss,1);//第二次dfs cout<<ans<<'\n'; return 0; }
习题:
数字转换
#include<cstdio> #include<cstdlib> #include<queue> using namespace std; int n; const int N=5e4+3; int sum[N]; vector <int> g[N]; void prepare() { for(int i=1;i<n;i++) for(int j=i<<1;j<=n;j+=i) sum[j]+=i; for(int i=1;i<=n;i++) if(sum[i]<i) g[i].push_back(sum[i]), g[sum[i]].push_back(i); } int ans; int d1[N],d2[N]; void dfs(int rt,int f) { int sz=g[rt].size() ; for(int i=0;i<sz;i++) { int nx=g[rt][i]; if(nx==f) continue; dfs(nx,rt); if(d1[nx]+1>d1[rt]) d2[rt]=d1[rt],d1[rt]=d1[nx]+1; else if(d1[nx]+1>d2[rt]) d2[rt]=d1[nx]+1; ans=max(ans,d1[rt]+d2[rt]); } } int main() { scanf("%d",&n); prepare(); dfs(1,0); printf("%d\n",ans); return 0; }
https://www.cnblogs.com/ymzjj/p/9767526.html
2>树上动态规划
例题:皇宫看守
一个点,只要父亲或者孩子节点被选即可,
求最小代价
仿照舞会设计状态,
找出有影响的状态,这里是分成三种:
(1)rt没选,fa选了,son不知道 《= son的状态为2、3
(2)rt没选,fa不知道,son选了 《= son的状态为3(一个为3,其他为2、3)
(3)rt选了,son可选可不选,fa可选可不选 《= son的状态为1,、2、3
就像原来是2,3两个状态,现在如果选了fa,rt和son都不用选,所以多了一个状态
但是因为统计的时候会复杂,所以虽然状态中要求选fa,但是我们统计的时候,值是不包括fa的
#include<cstdio> #include<cstdlib> #include<queue> #include<algorithm> #include<cstring> using namespace std; int n; const int N=1503,inf=1<<30; int val[N],sz[N],f[N][3]; vector <int > g[N]; int root; void dfs(int rt) { f[rt][2]=val[rt]; int mn=inf; for(int i=0;i<sz[rt];i++) { int nx=g[rt][i]; dfs(nx); f[rt][0]+=min(f[nx][1],f[nx][2]); f[rt][1]+=min(f[nx][1],f[nx][2]); mn=min(mn,max(f[nx][2]-f[nx][1],0) ); f[rt][2]+=min(f[nx][0],min(f[nx][1],f[nx][2])); } f[rt][1]+=mn; } int main() { scanf("%d",&n); int u,v,w; for(int i=1;i<=n;i++) { scanf("%d",&u); if(!root) root=u; scanf("%d%d",&val[u],&sz[u]); for(int j=0;j<sz[u];j++) scanf("%d",&v),g[u].push_back(v); } dfs(root); printf("%d\n",min(f[root][1],f[root][2])); return 0; }
3>树上背包dp
就跟背包一样做就好了
#include<cstdio> #include<cstdlib> #include<vector> using namespace std; int n,m; const int N=303; int d[N]; int f[N][N]; vector <int> g[N]; void dfs(int rt,int sum) { if(!sum) return ; if(f[rt][sum]) return ; for(int i=1;i<=sum;i++) f[rt][i]+=d[rt]; int p=sum-1; int sz=g[rt].size() ; for(int i=0;i<sz;i++) { int nx=g[rt][i]; dfs(nx,p); for(int k=sum;k>1;k--) for(int j=1;j<k;j++) f[rt][k]=max(f[rt][k],f[rt][k-j]+f[nx][j]); } } int main() { scanf("%d%d",&n,&m); int fa; for(int i=1;i<=n;i++) { scanf("%d%d",&fa,&d[i]); g[fa].push_back(i); } dfs(0,m+1); printf("%d\n",f[0][m+1]); return 0; }