树的直径板子
一. 两次 bfs/dfs;
可行的原因是有个性质,从树的任意结点遍历整棵树,最后会到达直径的一个端点,然后再用这个端点再遍历一次树就可以得到树的直径;
// 前向星存树
void bfs(int u){
mem(st,0);
queue<int> q;
q.push(u);
st[u] = 1;
while(q.size()){
int t = q.front();
q.pop();
for(int i = h[t]; i != -1 ; i = e[i].ne){
int v = e[i].to;
if(st[v]) continue;
st[v] = 1;
d[v] = d[t]+ e[i].w;
if(d[v] > d[pos]) pos = v;
q.push(v);
}
}
}
void dfs(int u,int fa){
for(int i = h[u]; i != -1; i = e[i].ne){
int v = e[i].to;
if(v == fa) continue;
d[v] = d[u] + e[i].w;
if(d[v] > d[pos]) pos = v;
dfs(v,u);
}
}
dfs 代码更加简洁;
二.如何打印直径
//这里使用bfs来遍历;
//前向星的使用也有细节
int cnt = 1;
void add(int u,int v,int w){
e[++cnt] = {v,h[u],w};
h[u] = cnt;
}// 边的编号从2开始,把 1 空出来;
h[] 数组初始化为0
int pre[N],pos; // 用来记录当前结点与父亲结点相连的边的编号;
//pos 用来记录直径的一端;
void bfs(int u){
mem(st,0);mem(pre,0);
queue<int> q;
q.push(u);
st[u] = 1;
while(q.size()){
int t = q.front();
q.pop();
for(int i = h[t]; i != -1 ; i = e[i].ne){
int v = e[i].to;
if(st[v]) continue;
st[v] = 1;pre[v] = i;
d[v] = d[t]+ e[i].w;
if(d[v] > d[pos]) pos = v;
q.push(v);
}
}
}
// 遍历直径经过的结点;
vector<int>v;
void reback(){
int u = pos;
while(u){
v.push_back(u);
int p = pre[u];// 边编号
u = e[p^1].to;
}
}
for(auto i:v) cout << i <<" ";
三.打印路径边编号改变的原因;
成对变换
从上例题,可以看到当遍历到直径的端点后,pre[u] = pos = 0 ,即没有儿子了, 由于双向边更改边权的原因,会导致 pos ^ 1 变为1 就导致 最后会再次进入 e[1].to ,即u = 编号为1的边里存的子节点的编号,若该边存有值将会再次循环,从而导致reback函数 死循环,因而把 cnt = 1 空出来 初始化零从而跳出循环! 这点非常重要;
四.树形DP求树的直径
int st[],d[u];//是否来过该点// d 数组表示以 u结点 为根的最大边的值;
//证明:略; (23333);
void dp(int u,int &res){
st[u] = 1;
for(int i = h[u];i != -1; i = e[i].ne){
int v = e[i].to;
if(st[v]) continue;
dp(v,res);
res = max(res,d[v]+d[u]+e[i].w);
d[u] = max(d[u],d[v]+e[i].w);
}
}
//利用树形dp还可以求解直径的个数
//再开一个 num[u] 数组记录以u为根节点的最大边的个数,故答案就为 num[v]*num[u]的最大值;
int res = -inf, k = 0;//k 统计个数;
void dp(int u,int fa){
d[u] = 0;
num[u] = 1;
for(int i = h[u];i; i = e[i].ne){
int v = e[i].to;
if(v == fa) continue;
dp(v,u);
int temp = d[v]+ e[i].w;
if(temp+d[u] > res){
res = temp+d[u];
k = num[u]*num[v];
}else if(temp+d[u] == res){
k += num[u]*num[v];
}
if(temp > d[u]){
d[u] = temp;
num[u] = num[v];
}else if(temp == d[u])num[u] += num[v];
}
}
局部变量不要忘记赋初值!!!!
这道题很巧妙的融合了树的直径求解的两个方法 这里仅给出例题
题解参考此位大佬!
/*
author:@bzdhxs
date:2021//
URL:https://www.cnblogs.com/gzh-red/p/11178619.html#%E7%AE%97%E6%B3%95%E6%B5%81%E7%A8%8B;
知识点: 树的直径;
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
#define _orz ios::sync_with_stdio(false),cin.tie(0)
#define mem(str,num) memset(str,num,sizeof(str))
typedef long long ll;
const int N = 2e5;
int n,k;
struct node{
int to,ne,w;
}e[N<<1];
int cnt = 1,h[N],d[N];
int pre[N],st[N],res;
void init(){
mem(h,0);
cnt = 1;
}
vector<int> v;
void add(int u,int v,int w){
e[++cnt] = {v,h[u],w};
h[u] = cnt;
cout <<"u = "<< u <<" "<< "e[cnt].to = " << e[cnt].to <<" "<< "e[cnt].ne = " << e[cnt].ne <<" "<< "h[u] = " << h[u] << endl;
}
int bfs(int u){
int pos;
mem(st,0);mem(d,0);mem(pre,0);
st[u] = 1;
queue<int> q;
q.push(u);
while(q.size()){
int t = q.front();
q.pop();
for(int i = h[t]; i ; i = e[i].ne){
int v = e[i].to;
if(st[v]) continue;
st[v] = 1,d[v] = d[t]+ e[i].w;
pre[v] = i;
if(d[v] > d[pos]) pos = v;
q.push(v);
}
}
return pos;
}
void dptree(int u,int & res){
st[u] = 1;
for(int i = h[u]; i ; i = e[i].ne){
int v = e[i].to;
if(st[v]) continue;
dptree(v,res);
res = max(res,d[u]+d[v]+e[i].w);
d[u] = max(d[u],d[v]+e[i].w);
}
}
void work(){
int p = bfs(1);
p = bfs(p);
res = (n-1)*2 - d[p] + 1;
if(k == 2){
int u = p;
while(u){
v.push_back(u);
int pos = pre[u];
cout << "u = "<< u <<" "<< "pre[u] = "<< pos << " ";
e[pos].w = e[pos^1].w = -1; //---- 处理双边
u = e[pos^1].to;
cout <<" pos^1 = "<< (pos^1)<<" "<<"e[pos^1].to = "<< u << endl;
}
for(auto i:v) cout << i <<" ";
int ans = 0;// 不忘记给初值
mem(d,0);mem(st,0);
dptree(1,ans);
res += 1 - ans;
}
cout << res << endl;
}
int main(){
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
_orz;
init();
cin >> n >> k;
for(int i = 1;i <= n-1;i++){
int v,u;cin>>v>>u;
add(v,u,1);
add(u,v,1);
}
work();
}
总结:
1.搜索的优点在于可以记录路径 缺点是 无法处理负边权;
2.dp代码简洁 缺点是无法求出路径