树的直径与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上去的,然后这道题不让再交题解了,哭晕