洛谷题单【图论2-1】基础树上问题

P5836 [USACO19DEC]Milk Visits S

很巧妙的思路
洛谷题解

P3379 【模板】最近公共祖先(LCA)

思路

Pecco的知乎文章
讲的很好!

实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int e[50010<<1],ne[50010<<1]; 
int head[500010], tot;
void add(int x, int y) {
	e[++tot] = y;
	ne[tot] = head[x];
	head[x] = tot;
}
int depth[500001], fa[500001][22], lg[500001];
//预处理出fa数组 
void dfs(int now, int fath) {
	fa[now][0] = fath; 
	depth[now] = depth[fath] + 1;
	for(int i = 1; i <= lg[depth[now]]; ++i)
		fa[now][i] = fa[fa[now][i-1]][i-1];
	for(int i = head[now]; i; i = ne[i])
		if(e[i]!= fath) dfs(e[i], now);
}
int LCA(int x, int y) {
	if(depth[x] < depth[y]) 
		swap(x, y);
	while(depth[x] > depth[y]) 
		x = fa[x][lg[depth[x]-depth[y]] - 1];
	if(x == y) 
		return x;
	for(int k = lg[depth[x]] - 1; k >= 0; --k)
		if(fa[x][k] != fa[y][k])
			x = fa[x][k], y = fa[y][k];
	return fa[x][0];
}
int main() 
{
	int n, m, s; scanf("%d%d%d", &n, &m, &s);
	for(int i = 1; i <= n-1; ++i) {
		int x, y; scanf("%d%d", &x, &y);
		add(x, y); add(y, x);  //建图的时候双向 
	}
	for(int i = 1; i <= n; ++i)
		lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //预处理lg数组 
	dfs(s, 0);
	for(int i = 1; i <= m; ++i) {
		int x, y; scanf("%d%d",&x, &y);
		printf("%d\n", LCA(x, y));
	}
	return 0;
}

P1395 会议

思路

树的重心:对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。
(这里以及下文中的“子树”都是指无根树的子树,即包括“向上”的那棵子树,并且不包括整棵树自身。)

求树的重心:

  • 在 DFS 中计算每个子树的大小,记录“向下”的子树的最大大小,利用总点数 - 当前子树(这里的子树指有根树的子树)的大小得到“向上”的子树的大小,然后就可以依据定义找到重心了。

性质:

  • 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。

  • 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。

  • 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。

  • 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

由第二条性质可知本题只需要dfs求出树的重心,再bfs求出所有点到重心的距离和。

实现

//来自洛谷题解区 加勒比·史努比
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;  
const int N=50010;  
int n,ans; 
int maxn=10000000;
int u,r,sum; 
int d[N],dis[N]; 
vector<int> G[N]; 
queue<int> q; 
bool vis[N];  
void dfs(int s,int f){  //求树的重心
    d[s]=1;
    int res=0;
    for(int i=0;i<G[s].size();i++){
        if(G[s][i]==f) continue;
        dfs(G[s][i],s);
        d[s]+=d[G[s][i]];
        res=max(res,d[G[s][i]]);
    }
    res=max(res,n-d[s]);
    if(res<maxn||(res==maxn&&ans>s)){
        maxn=res;
        ans=s;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&u,&r);
        G[u].push_back(r);
        G[r].push_back(u);
    }
    dfs(1,0);
    q.push(ans);
    while(!q.empty()){  //BFS
        int e=q.front();
        q.pop();
        vis[e]=true;
        sum+=dis[e];
        for(int i=0;i<G[e].size();i++){
            if(!vis[G[e][i]]){
                q.push(G[e][i]);
                dis[G[e][i]]=dis[e]+1;
            }
        }
    }
    printf("%d %d",ans,sum); 
    return 0;
}

P5536 【XR-3】核心城市

思路

k个点中必然有树的直径的中点,两次dfs求树的直径,第三次dfs求出以直径中点为根,其他节点的深度和从该节点出发最远能到达的节点深度,把其他节点按照最大深度-这个点的深度降序排序,前k个成为中心城市。第k+1个城市的最大深度-这个点的深度+1即为答案。
(因为最大深度-这个点的深度就是该点到叶结点的距离,该点是第k+1个点,有k个中心城市,那么这个点一定和中心城市直接相连,+1即可)

实现

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 100010
int n,k,zj,num,ans_k;
int cut,head[N],ver[2*N],ne[2*N];
int deep[N],f[N],maxdeep[N],ans[N];
bool cmp(int a,int b){
	return a>b;
}
void add(int x,int y){
	ver[++cut]=y;ne[cut]=head[x];head[x]=cut;
}
//求直径
void dfs1(int x,int fa){
	if(deep[x]>zj){
		zj=deep[x];
		num=x;
	}
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(y==fa)continue;
		deep[y]=deep[x]+1;
		dfs1(y,x);
	}
}
void dfs2(int x,int fa){
	if(deep[x]>zj){
		zj=deep[x];
		num=x;
	}
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(y==fa)continue;
		deep[y]=deep[x]+1;
		f[y]=x;
		dfs2(y,x);
	}
}
//
void dfs_k(int x,int fa){
	maxdeep[x]=deep[x];
	for(int i=head[x];i;i=ne[i]){
		int y=ver[i];
		if(y==fa)continue;
		deep[y]=deep[x]+1;
		dfs_k(y,x);
		maxdeep[x]=max(maxdeep[x],maxdeep[y]);
	}
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;++i){
    	int x,y;
    	scanf("%d%d",&x,&y);
    	add(x,y);
    	add(y,x);
	}
    //直径
	dfs1(1,0);
	memset(deep,0,sizeof(deep));
	zj=0;
	dfs2(num,0);
    //
	int kkk=num;
    //找直径的中点
	for(int i=1;i<=(deep[num]+1)/2;++i)kkk=f[kkk];
	memset(deep,0,sizeof(deep));
    //再搜一次
	dfs_k(kkk,0);
	for(int i=1;i<=n;++i)ans[i]=maxdeep[i]-deep[i];
	sort(ans+1,ans+n+1,cmp);
	printf("%d\n",ans[k+1]+1);
    return 0;
}

欢迎指正-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值