树的直径与LCA

树的直径与LCA


无语了,我树的直径都能写错

树的直径大概有两种写法,树形dp和bfs。

树形dp:以1节点为根节点,考虑d[i]表示以i为根节点,从i走向i的子树的最远长度,显然d[i]可以用dfs,O(n)求解。 考虑f[i],表示经过i点的最长链的长度,maxf[1~n]就是树的直径,因为树的直径由四部分组成,又因为d[i]存储的是最大值,所以有很巧的方法可以一遍O(n),求出d[i],顺便求出树的直径。

int dp(int u,int fa)
{
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==fa)continue;
        dfs(j,u);
        ans=max(ans,d[u]+d[j]+w[i]);
        d[u]=max(d[u],d[j]+w[i]);
    }
}
ans就是树的直径,d[]就是上边的d数组

bfs求树的直径,两次bfs,还可以得到直径的具体方案

首先任取一个点p进行bfs,得到与p点最远的点q,可以证明q一定在树的直径上(反证法)。

因为q在树的直径上,在q点再bfs一次就能得到树的直径。

int len=-1,point;
int pre[N];
void bfs(int u)
{
	mem(dis,0),mem(vis,0);
	vis[u]=1;
	queue<int>que;
	que.push(u);
	while(que.size())
	{
		int t=que.front();
		que.pop();
		for(int i=h[t];~i;i=ne[i])
		{
			int j=e[i];
			if(vis[j])continue;
            pre[j]=t;//打印
			que.push(j);
			vis[j]=1;
			dis[j]=dis[t]+w[i];
		}
	}
	
	for(int i=1;i<=n;i++){
		if(dis[i]>=len){
			len=dis[i];
			point=i;
		}
	}
}
	bfs(1);//从任意1个节点进行bfs 
	bfs(point);

打印从起点到终点
void print(int x)
{
	if(!pre[x]){
		cout<<x<<" ";
		return ;
	}
	print(pre[x]);
	cout<<x<<" ";
}

打印从终点到起点
void print(int x)
{
	if(!pre[x]){
		cout<<x<<" ";
		return ;
	}
	cout<<x<<" ";//就是换了下位置
    print(pre[x]);
}

P5536 【XR-3】核心城市(树直径中点)


题目描述

给一棵 n n n个节点的无根树,树边权为 1 1 1,选择 k k k个两两可达的节点作为一个群,定义其他节点到群的距离是其他节点到该群距离的最小值,对于所有不是群中的节点,找到其中与群距离最大的点,使其与群的距离最小,求最小值是多少。


分析

一开始看完之后看不懂题,很像二分答案,但是没见过树上的,然后点开了题解,如果 k = 1 k=1 k=1的话,那么 k k k应该是树直径的中点,然后以该点为根,逐渐扩展群中的点,使用什么贪心的策略扩大点的集合呢?

可以画图考虑,
在这里插入图片描述

很明显为了得到最小距离,1号节点应该扩展4号节点,以树直径中点 u u u为根 d f s dfs dfs,设 m a x d e e p [ i ] maxdeep[i] maxdeep[i]表示 i i i点可以向下走的最大深度, d e p t h [ i ] depth[i] depth[i]是i点的深度, d i s [ i ] = m a x d e e p [ i ] − d e p t h [ i ] dis[i]=maxdeep[i]-depth[i] dis[i]=maxdeep[i]depth[i],将 d i s dis dis数组从大到小排序后, d i s [ k + 1 ] + 1 dis[k+1]+1 dis[k+1]+1就是答案, d i s [ i ] dis[i] dis[i]就是 i i i点到群的最大距离 − 1 -1 1

#include<bits/stdc++.h>
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;

typedef long long ll;
typedef unsigned long long ull; 
const int N=1e5+10,M=1e9+7;

int h[N],e[N*2],ne[N*2],idx;
int n,k;
void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int dis[N],pre[N],point;
int path[N],mid;//为了找树直径的中点
bool vis[N];

void bfs(int u){
	vis[u]=1;
	queue<int>que;
	que.push(u);
	while(que.size()){
		int t=que.front();
		que.pop();
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(vis[j])continue;
			pre[j]=t;
			que.push(j);
			vis[j]=1;
			dis[j]=dis[t]+1;
		}
	}
	int len=-1;
	for(int i=1;i<=n;i++){
		if(dis[i]>=len){
			len=dis[i];
			point=i;
		}
	}
}

void print(int x)//使用递归来找中点
{
	if(!pre[x]){
		path[++mid]=x;	
		return ;
	}
	print(pre[x]);
	path[++mid]=x;
}
/*
   dis[i]=maxdeep[i]-depth[i]; 
*/
int dep[N],maxdep[N],ans[N];
int dfs(int u,int fa,int depth)
{
	dep[u]=depth;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa)continue;
		maxdep[u]=max(maxdep[u],dfs(j,u,depth+1));
	}
	return maxdep[u]=max(maxdep[u],dep[u]);
}

int main()
{
	cin>>n>>k;
	mem(h,-1);
	for(int i=0;i<n-1;i++){
		int a,b;cin>>a>>b;
		add(a,b),add(b,a);
	}
	
	bfs(1);
	mem(dis,0);mem(vis,0);mem(pre,0);
	bfs(point);
	print(point);
	mid=path[(1+mid)/2];//直径中点 
	
	dfs(mid,-1,0);
	
	for(int i=1;i<=n;i++){
		ans[i]=maxdep[i]-dep[i];
	}
	sort(ans+1,ans+n+1,greater<int>());
	
	
	cout<<ans[k+1]+1<<endl;
	
    return 0;
}

本来想交到luogu上去的,然后这道题不让再交题解了,哭晕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值