简单树上问题
1. 树的重心
重心的定义:以某一节点为根,如果该节点的最大子树的节点最小,则u为树的重心(也就是子树最均衡)
const int N = 50005; //最大结点数
struct Edge{ int to, next;} edge[N<<1]; //两倍:u-v, v-u
int head[N], cnt = 0;
void init(){ //链式前向星:初始化
for(int i=0; i<N; ++i){
edge[i].next = -1;
head[i] = -1;
}
cnt = 0;
}
void addedge(int u,int v){ //链式前向星:加边u-v
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int n;
int d[N], ans[N], num=0, maxnum=1e9; //d[u]: 以u为根的子树的结点数量
void dfs(int u,int fa){
d[u] = 1; //递归到最底层时,结点数加1
int tmp = 0;
for(int i=head[u]; ~i; i=edge[i].next){ //遍历u的子结点。~i也可以写成i!=-1
int v = edge[i].to; //v是一个子结点
if(v == fa) continue; //不递归父亲
dfs(v,u); //递归子结点,计算v这个子树的结点数量
d[u] += d[v]; //计算以u为根的结点数量
tmp = max(tmp,d[v]); //记录u的最大子树的结点数量
}
tmp = max(tmp, n-d[u]); //tmp = u的最大连通块的结点数
//以上计算出了u的最大连通块
//下面统计疑似教父。如果一个结点的最大连通块比其他结点的都小,它是疑似教父
if(tmp < maxnum){ //一个疑似教父
maxnum = tmp; //更新“最小的”最大连通块
num = 0;
ans[++num] = u; //把教父记录在第1个位置
}
else if(tmp == maxnum) ans[++num] = u; //疑似教父有多个,记录在后面
}
int main(){
scanf("%d",&n);
init();
for(int i=1; i<n; i++){
int u, v; scanf("%d %d", &u, &v);
addedge(u,v); addedge(v,u);
}
dfs(1,0);
sort(ans+1, ans+1+num);
for(int i=1;i<=num;i++) printf("%d ",ans[i]);
}
2. 树的直径
树的直径是树上最远的两点间的距离,又被称为树的最远点对。
1. 两次DFS
- 需要完整的路径
- 不可以有负权边
const int N=1e5+10;
struct edge{ int to,w;}; //to: 边的终点 w:权值
vector<edge> e[N]; //用邻接表存边
int dist[N]; //记录距离
void dfs(int u,int father,int d){ //用dfs计算从u到每个子结点的距离
dist[u]=d;
for(int i=0;i<e[u].size();i++)
if(e[u][i].to != father) //很关键,这一句保证不回头搜父结点
dfs(e[u][i].to, u, d + e[u][i].w);
}
int main(void){
int n; cin>>n;
for(int i=0;i<n-1;i++){
int a,b,w; cin>>a>>b>>w;
e[a].push_back({b,w}); //a的邻居是b,边长w
e[b].push_back({a,w}); //b的邻居是a
}
dfs(1,-1,0); //计算从任意点(这里用1号点)到树上每个结点的距离
int s = 1;
for(int i=1;i<=n;i++) //找最远的结点s, s是直径的一个端点
if(dist[i]>dist[s]) s = i;
dfs(s,-1,0); //从s出发,计算以s为起点,到树上每个结点的距离
int t = 1;
for(int i=1;i<=n;i++) //找直径的另一个端点t
if(dist[i]>dist[t]) t = i;
cout << dist[t]<<endl; //打印树的直径的长度
return 0;
}
2. 树形DP
只可以求长度
可以求负权边
const int N=1e5+10;
struct edge{int to,w; }; //to: 边的终点 w:权值
vector<edge> e[N];
int dp[N];
int maxlen = 0;
bool vis[N];
void dfs(int u){
vis[u] = true;
for(int i = 0; i < e[u].size(); ++ i){
int v = e[u][i].to, edge = e[u][i].w;
if(vis[v]) continue; //v已经算过
dfs(v);
maxlen = max(maxlen, dp[u]+ dp[v]+ edge);
//计算max{f[u]}。注意此时dp[u]不包括v这棵子树,下一行才包括
dp[u] = max(dp[u], dp[v] + edge); //计算dp[u],此时包括了v这棵子树
}
return ;
}
int main(){
int n; cin >> n;
for(int i = 0; i < n-1; i++){
int a, b, w; cin >> a >> b >> w;
e[a].push_back({b,w}); //a的邻居是b,路的长度w
e[b].push_back({a,w}); //b的邻居是a
}
dfs(1); //从点1开始DFS
cout << maxle
n << endl;
return 0;
}