一道题学习求树的直径的两种方法--- 树的直径模板;

树的直径板子

一. 两次 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代码简洁 缺点是无法求出路径

猜你喜欢

转载自blog.csdn.net/qq_51687628/article/details/120618386