目录

  • 定义:
  • 性质:
  • 算法分析:
  • POJ 1655 Balancing Act(求重心)
  • POJ 3107 Godfather
  • P1364 医院设置(树形DP)

定义:

树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。

树的重心定义为树的某个节点,当去掉该节点后,树的各个连通分量中,节点数最多的连通分量其节点数达到最小值。树可能存在多个重心。如下图,当去掉点1后,树将分成两个连通块:(2,4,5),(3,6,7),则最大的连通块包含节点个数为3。若去掉点2,则树将分成3个部分,(4),(5),(1,3,6,7)最大的连通块包含4个节点;第一种方法可以得到更小的最大联通分量。可以发现,其他方案不可能得到比3更小的值了。所以,点1是树的重心


来源博客

性质:

  1. 树上所有的点到树的重心的距离之和是最短的,如果有多个重心,那么总距离相等。
  2. 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上
  3. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置
  4. 一棵树最多有两个重心,且相邻。

算法分析:

和树的最大独立问题类似,先任选一个结点作为根节点,把无根树变成有根树,然后设 d[i]d[i]d[i] 表示以i为根的子树的结点的个数。不难发现d[i]=∑d[j]+1d[i]=∑d[j]+1d[i]=∑d[j]+1,j∈s[i]j∈s[i]j∈s[i]。s[i]s[i]s[i]为i结点的所有儿子结点的编号的集合。程序也十分简单:只需要DFS一次,在无根树转有根数的同时计算即可,连记忆化都不需要——因为本来就没有重复计算。
那么,删除结点i后,最大的连通块有多少个呢?结点i的子树中最大有max(d[j])max({d[j]})max(d[j])个结点,i的“上方子树”中有n−d(i)n-d(i)n−d(i)个结点,这样,在动态规划的过程中就可 以顺便找出树的重心了。


以上内容来自《算法竞赛入门经典》

POJ 1655 Balancing Act(求重心)

Balancing Act
题意:给定一棵树,求树的重心的编号以及重心删除后得到的最大子树的节点个数size,如果size相同就选取编号最小的.
Sample Input

1
7
2 6
1 2
1 4
4 5
3 7
3 1

Sample Output

1 2
#include<string.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<double,ll>pdl;
#define debug(x) cerr<<"# "<<x<<endl
const ll N=20005;
const ll base=137;
const ll mod=2147483647;
const int INF = 1<<30;
//const double INF=double(INT_MAX*1.0);
ll head[N];//链式前向星
ll son[N];//son[i]表示以i为根的子树节点个数
ll cnt,n;
ll ans,size;
bool vis[N];//代替了其他做法里的fa  (father)
struct Edge//链式前向星
{ll to;ll nex;
};
Edge edge[2*N];
inline void init()//初始化
{cnt=0;size=INF;memset(vis,0,sizeof vis);memset(head,-1,sizeof head);
}
inline void add(ll u,ll v)//链式前向星
{edge[cnt].to=v;edge[cnt].nex=head[u];head[u]=cnt++;
}
inline void dfs(ll cur)
{vis[cur]=1;son[cur]=1;//节点本身ll tmp=0;//tmp为节点cur的最大子树节点个数 for(ll i=head[cur];~i;i=edge[i].nex){ll v=edge[i].to;if(!vis[v])//if(v!=fa){dfs(v);son[cur]+=son[v];tmp=max(tmp,son[v]);//同上}}tmp=max(tmp,n-son[cur]);//更新,最大子树的节点个数sizeif(tmp<size||(tmp==size&&cur<ans))//更新,要最小的那一个才是重心{ans=cur;size=tmp;}
}
int main()
{ll T;scanf("%lld",&T);while(T--){init();scanf("%lld",&n);for(int i=1;i<=n-1;++i){ll u,v;scanf("%lld %lld",&u,&v);add(u,v);//无向图必须双向add(v,u);}dfs(1);printf("%lld %lld\n",ans,size);}return 0;
}

POJ 3107 Godfather

Godfather
题意:给定一棵树,求树的所有重心,按照编号从小到大的顺序输出.

分析:本题与上题基本上一样,只是求的量不同,既然我们在找树的重心的时候用的树型dp,而且是求的子树中节点数的最大值,然后求所有最大值的最小值,那么就有可能存在多个重心,我们每更新到一个最小值的时候就记录其它的最小值也为这个最小值的重心,这样下去就会找到所有的重心.

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>using namespace std;
const int N = 50005;
const int INF = 1<<30;int head[N];
int son[N];
bool vis[N];
int cnt,n;
int num,size;
int ans[N];struct Edge
{int to;int next;
};Edge edge[2*N];void Init()
{cnt = 0;num = 0;size = INF;memset(vis,0,sizeof(vis));memset(head,-1,sizeof(head));
}void add(int u,int v)
{edge[cnt].to = v;edge[cnt].next = head[u];head[u] = cnt++;
}void dfs(int cur)
{vis[cur] = 1;son[cur] = 0;int tmp = 0;for(int i=head[cur];~i;i=edge[i].next){int u = edge[i].to;if(!vis[u]){dfs(u);son[cur] += son[u] + 1;tmp = max(tmp,son[u] + 1);}}tmp = max(tmp,n-son[cur]-1);if(tmp < size){num = 1;ans[0] = cur;size = tmp;}else if(tmp == size){ans[num++] = cur;}
}int main()
{while(~scanf("%d",&n)){Init();for(int i=1;i<=n-1;i++){int u,v;scanf("%d%d",&u,&v);add(u,v);add(v,u);}dfs(1);sort(ans,ans+num);for(int i=0;i<num;i++)printf("%d ",ans[i]);puts("");}return 0;
}

P1364 医院设置(树形DP)

洛谷P1364 医院设置
定义几个数组:f[u]f[u]f[u]表示以u为根的总距离,size[u]size[u]size[u]表示以u为根的子树的大小(结点数,此题每个点要乘以权值,下文结点数均指此)。

显然,ans=min(f[i],1<=i<=n)ans=min(f[i],1<=i<=n)ans=min(f[i],1<=i<=n)
首先我们任意以一个点为根dfs一遍,求出以该点为根的总距离。方便起见,我们就以1为根。

接下来就是转移,对于每个u能达到的点v,有:

f[v]=f[u]+size[1]−size[v]−size[v]f[v]=f[u]+size[1]−size[v]−size[v]f[v]=f[u]+size[1]−size[v]−size[v]

怎么来的呢?试想,当根从u变为v的时候,v的子树的所有节点原本的距离要到u,现在只要到v了,每个结点的距离都减少1,那么总距离就减少size[v]size[v]size[v],同时,以v为根的子树以外的所有节点,原本只要到u就行了,现在要到v,每个节点的路程都增加了1,总路程就增加了size[1]−size[v]size[1]−size[v]size[1]−size[v],其中size[1]size[1]size[1]就是我们预处理出来的整棵树的大小,减去size[v]size[v]size[v]就是除以v为根的子树以外的结点数。

最后取最小值,得解。时间复杂度O(n)O(n)O(n)
上述思路来源

#include<string.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<double,ll>pdl;
#define debug(x) cerr<<"# "<<x<<endl
const ll N=20005;
const ll base=137;
const ll mod=2147483647;
const int INF = 1<<30;
struct Edge
{ll to;ll nex;
}tree[N];
ll head[N],cnt,w[N],n,size[N];
ll ans=INF,f[N];
inline void add(ll u,ll v)//链式前向星建树
{tree[++cnt].to=v;tree[cnt].nex=head[u];head[u]=cnt;
}
inline void init()//初始化
{memset(head,-1,sizeof head);cnt=0;
}
inline void dfs(ll u,ll fa,ll dep)//以u为根,father为fa,深度为dep
{size[u]=w[u];//size[u]表示以u为根的子树的大小(结点数,此题每个点要乘以权值,正常情况下应该是1)for(int i=head[u];~i;i=tree[i].nex)//遍历所有相连的子树{if(tree[i].to!=fa){dfs(tree[i].to,u,dep+1);//往下走size[u]+=size[tree[i].to];}}f[1]+=w[u]*dep;//f[u]表示以u为根的总距离,先预处理求得以1为根,f[1]的值
}
inline void dp(ll u,ll fa)
{for(int i=head[u];~i;i=tree[i].nex){if(tree[i].to!=fa)//防止自环{//通过转移方程直接把所有的点当作根的deep算出来然后比较f[tree[i].to]=f[u]+size[1]-size[tree[i].to]*2;dp(tree[i].to,u);}}ans=min(ans,f[u]);
}
int main()
{init();scanf("%lld",&n);for(int i=1;i<=n;++i){ll a,b;scanf("%lld",&w[i]);scanf("%lld %lld",&a,&b);if(a)add(i,a),add(a,i);if(b)add(i,b),add(b,i);}dfs(1,0,0);dp(1,0);printf("%lld\n",ans);return 0;
}

【树形DP】树的重心详解+多组例题详解相关推荐

  1. POJ - 4045 Power Station(树形dp/树的重心)

    题目链接:点击查看 题目大意:给出一个n个节点的树,我们需要选出一个节点,到其余任何节点的距离和最小 题目分析:这个题我的第一反应是用树的重心,先求出来符合条件的点,然后再跑一遍dfs求距离,最后输出 ...

  2. 树形dp——树的重心(2) 代码调试理解

    和树的最大独立问题类似,先任选一个结点作为根节点,把无根树变成有根树,然后设d(i)表示以i为根的子树的结点的个数.不难发现d(i)=∑d(j)+1,j∈s(i).s(i)为i结点的所有儿子结点的编号 ...

  3. 树形dp树的重心(D - Godfather POJ - 3107)

    题目链接:https://cn.vjudge.net/contest/277955#problem/D 题目大意:求树的重心(树的重心指的是树上的某一个点,删掉之后形成的多棵树中节点数最大值最小). ...

  4. 树形dp ——树的重心

    1.只需要求出最大子树中节点数最小的数目即可 题意:有一个国王要把他的领土分给两个儿子,国王的领土是一棵树,N个结点,N-1条边把这些结点连起来,现在大小儿子要选择一个点作为他的首都,那么除首都分别是 ...

  5. 树形DP+树状数组 HDU 5877 Weak Pair

    1 //树形DP+树状数组 HDU 5877 Weak Pair 2 // 思路:用树状数组每次加k/a[i],每个节点ans+=Sum(a[i]) 表示每次加大于等于a[i]的值 3 // 这道题要 ...

  6. [BZOJ3197][Sdoi2013]assassin(树形DP+树同构+二分图最优匹配)

    关于树同构,有一个神奇的性质: 一棵树的重心只有 111 个或 2" role="presentation" style="position: relative ...

  7. HDU - 5242 Game(树形dp+树链剖分/树上贪心+思维)

    题目链接:点击查看 题目大意:给出一棵包含n个节点的树,每个节点都有一个权值,整棵树的根是点1,问从点1开始向下一直走到叶子节点,可以走k次,怎么样走权值和最大,每个节点被走过一次后权值会变为0 题目 ...

  8. BZOJ 1827: [Usaco2010 Mar]gather 奶牛大集会 树形DP + 带权重心

    Description Bessie正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会.当然,她会选择最方便的地点来举办这次集会.每个奶牛居住在 N(1<=N<=100,0 ...

  9. 洛谷4895 BZOJ3162 独钓寒江雪 树形dp 树哈希

    题目链接 题意: 给定一棵无根树,求其中本质不同的独立集的个数.独立集就是一个集合中的点之间都没有边直接相连.n<=5e5n<=5e5n<=5e5,对1e9+71e9+71e9+7取 ...

最新文章

  1. ActiveMQ学习(七)
  2. 宽度定死、按照行间距、字体算出label高度
  3. 【12c新特性】12c中如何自动启动PDB Pluggable Database
  4. django-pagination 样式修改
  5. cellphonedb 及其可视化
  6. QT5+android_ubuntu软件源
  7. UE4 远程调用函数
  8. dbms数据库管理系统_数据库管理系统(DBMS)中的视图
  9. 计算机仿真在电力领域的应用,仿真技术在电力系统中的应用实例
  10. 周末项目:使用scikit-learn进行手语和静态手势识别
  11. java面试算法总结_面试10大算法汇总——Java篇
  12. 六个问题让你更懂 React Fiber
  13. 第三季-第23课-Linux网络编程模型
  14. Docker容器中bash: ip: command not found
  15. steam授权_验号机器人正式上线,支持检验csgo账号、steam账号信息
  16. 判断在ios系统中打开微信浏览器
  17. 基于Token实现开放API接口签名验证
  18. ​DOCX 文档解析及隐藏信息提取算法
  19. 微信公众号上传永久图片素材(将阿里云图片上传至微信公众号图片素材)
  20. 数论概论 第三章 勾股数组与单位圆

热门文章

  1. OpenVINO开发教程之八 – 道路分割
  2. vivado 2018与modelsim的联合仿真
  3. Python 23天 序列化
  4. 11g新特性:X$DBGALERTEXT一个很酷的内部视图
  5. JAVA实现 springMVC方式的微信接入、实现消息自动回复
  6. Android开发常用框架汇总
  7. redis学习笔记---redis的哨兵Sentinel
  8. Everest 0.6 设置ADSL上网
  9. swt能单独在linux运行么,java – 在Mac上运行基于SWT的跨平台jar
  10. 查看oracle已经锁定的表,Oracle中查询被锁定的表