树形dp求解树的重心和例题
树的重心:https://oi-wiki.org/graph/tree-centroid/
定义
对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。(最大的子树最小)
性质
- 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
- 树中所有 点到某个点的距离和 中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
- 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
- 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
求法
树的算法几乎都是建立在dfs上,遍历整棵树的过程中记录 所有点 作为根节点中 所有子树的节点数,树形dp,再遍历dp数组。
例题1:POJ1655
题意:n个点的树找重心,如果有两个,输出编号小的。
思路:以任意一点(一般直接选1)为根进行dfs,在遍历过程中,其中有2个变量,num[x]表示以x为根的子树的节点数量(包含自身),dp[x]表示若是以x为重心删除,拆出来的子树 的最大节点数量是多少?根据重心定义,最大的子树最小。
#include<stdio.h> #include<iostream> #include<algorithm> #include<cstring> #include<math.h> #include<string> #include<map> #include<queue> #include<stack> #include<set> #include<ctime> #define ll long long #define inf 0x3f3f3f3f const double pi=3.1415926; using namespace std; vector<int>a[20005]; int vis[20005]; int num[20005]; int dp[20005]; int t,n,k; void dfs(int x,int last)///当前点,父节点 { dp[x]=0;num[x]=1;///num[x]表示以自己为根节点的树有多少个节点,自己算一个,后续累加子树带来的贡献。 printf("开始 dp[%d]=%d num[%d]=%d \n",x,dp[x],x,num[x]); int res=1;///自己是一个节点,看看有没有没有剪掉的子节点贡献给自己顺便返回给父亲 for(int i=0;i<a[x].size();i++) { int next=a[x][i]; if(next==last) continue; dfs(next,x); dp[x]=max(dp[x],num[next]); num[x]+=num[next]; } //printf("dp[%d]=%d\n",x,dp[x]); dp[x]=max(dp[x],n-num[x]); printf("结束 dp[%d]=%d num[%d]=%d \n",x,dp[x],x,num[x]); } int main() { scanf("%d",&t); while(t--) { scanf("%d",&n); for(int i=1;i<=n;i++) { a[i].clear(); vis[i]=0; num[i]=0; } for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); a[x].push_back(y); a[y].push_back(x); } dfs(1,-1); int idx=1,cnt=dp[1]; for(int i=2;i<=n;i++) { if(dp[i]<cnt) idx=i,cnt=dp[i]; } printf("%d %d\n",idx,cnt); } return 0; }
例题2:https://ac.nowcoder.com/acm/contest/4479/C
题意:n个点的树,把某一个点作为根节点root,根节点的深度deeproot为0,其他节点到这个根节点的深度为deepi,求所有点的深度和最小是多少。
思路:根据第2条性质,树形dp找重心,再来一次dfs计算深度和。
#include<stdio.h> #include<iostream> #include<algorithm> #include<cstring> #include<math.h> #include<string> #include<map> #include<queue> #include<stack> #include<set> #include<ctime> #define ll long long #define inf 0x3f3f3f3f const double pi=3.1415926; using namespace std; vector<int>a[1000005]; int n,w=0;; int vis[1000005]; int dp[1000005]; int num[1000005]; void find(int x,int last)///当前点,父节点 { dp[x]=0;num[x]=1;///num[x]表示以自己为根节点的树有多少个节点,自己算一个,后续累加子树带来的贡献。 int res=1;///自己是一个节点,看看有没有没有剪掉的子节点贡献给自己顺便返回给父亲 for(int i=0;i<a[x].size();i++) { int next=a[x][i]; if(next==last) continue; find(next,x); dp[x]=max(dp[x],num[next]); num[x]+=num[next]; } dp[x]=max(dp[x],n-num[x]); } void dfs(int now,int deep){///计算深度和 vis[now]=1; w=w+deep; for(int i=0;i<a[now].size();i++){ if(vis[ a[now][i] ]==0){ dfs(a[now][i],deep+1); } } } int main(){ scanf("%d",&n); for(int i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); a[x].push_back(y); a[y].push_back(x); } find(1,-1); int idx=1,cnt=dp[1]; for(int i=2;i<=n;i++){///找重心 if(dp[i]<cnt) idx=i,cnt=dp[i]; } dfs(idx,0); printf("%d\n",w); return 0; }
天上不会掉馅饼,努力奋斗才能梦想成真!