蓝桥杯 砍树解析

题目描述:
给定一棵由 n 个结点组成的树以及 m 个不重复的无序数对 (a1, b1), (a2, b2),

. . . , (am, bm),其中 ai 互不相同,bi 互不相同,ai ≠ bj(1 ≤ i, j ≤ m)。

小明想知道是否能够选择一条树上的边砍断,使得对于每个 (ai , bi) 满足 ai和 bi 不连通,如果可以则输出应该断掉的边的编号(编号按输入顺序从 1 开始),否则输出 -1.

输入格式
输入共 n + m 行,第一行为两个正整数 n,m。

后面 n − 1 行,每行两个正整数 xi,yi 表示第 i 条边的两个端点。

后面 m 行,每行两个正整数 ai,bi。

输出格式
一行一个整数,表示答案,如有多个答案,输出编号最大的一个。

样例输入
6 2 1 2 2 3 4 3 2 5 6 5 3 6 4 5 4

样例输出
4

提示
断开第 2 条边后形成两个连通块:{3, 4},{1, 2, 5, 6},满足 3 和 6 不连通,4 和 5 不连通。

断开第 4 条边后形成两个连通块:{1, 2, 3, 4},{5, 6},同样满足 3 和 6 不连通,4 和 5 不连通。

4 编号更大,因此答案为 4。

对于 30% 的数据,保证 1 < n ≤ 1000。

对于 100% 的数据,保证 1 < n ≤ 105,1 ≤ m ≤ 2/n。

解题思路:
一开始的想法是查找给出的点对的所有共同祖先,然后按输入找出编号最大的那条边。但这样写太麻烦了,于是去看其他大神的代码有了下述解题代码及个人理解。

#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int>pii;
vector<int>edg[N];//存放所有的点(简化版的树)
int n, m;
int w[N];//存每一个边的边权,下标为边的编号
map<pii, int>id;//id存边的编号
int siz[N], dep[N], fa[N], son[N], top[N];
//siz存放当前节点的子节点数,dep存放当前节点的深度,fa当前节点父节点,son当前节点子树中包含子节点数最多的节点,top记录每一个重链的头
void dfs1(int u, int father)//u为当前节点,father为当前节点的父节点
{
	siz[u] = 1;
	dep[u] = dep[father] + 1;
	fa[u] = father;
	for (int i = 0; i < edg[u].size(); i++)
	{
		int s = edg[u][i];
		if (s == fa[u])
			continue;
		dfs1(s, u);
		siz[u] += siz[s];
		if (siz[son[u]] < siz[s])
			son[u] = s; //更新包含u节点这一子树中,节点树最多的点,这一点是u的子节点
	}
}
void dfs2(int u, int t)
{
	top[u] = t;
	if (son[u] == 0)
		return;
	dfs2(son[u], t);
	for (int i = 0; i < edg[u].size(); i++)
	{
		int s = edg[u][i];
		if (s == fa[u] || s == son[u])
			continue;
		dfs2(s, s);//这里说明s不在u这一条重链上,重新给s这一节点所在的重链的起始点赋值
		//dfs2(s, s) 的作用是找到以节点 s 为根的子树中各个节点所在的重链的起始点,并更新起始点的值。
	}
}
int lca(int x, int y)
{
	while (top[x] != top[y])//两点的重链不同
	{
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);
		x = fa[top[x]];//这里可以看作一个节点连接了两条重链(该节点的左右子树),
	}
	return dep[x]< dep[y] ? x : y;//两节点在同一重链上,取深度更小的那个
}
void cal_sum(int u, int father)
{
	for (int i = 0; i < edg[u].size(); i++)
	{
		int son = edg[u][i];
		if (son == father)
			continue;
		cal_sum(son, u);
		w[u] += w[son];//更新所有的边权
	}
}
void solve()
{
	cin >> n >> m;
	for (int i = 1; i <= n - 1; i++)
	{
		int x, y;
		cin >> x >> y;
		edg[x].push_back(y);
		edg[y].push_back(x);
		id[{x, y}] = i;
		id[{y, x}] = i;
	}
	dfs1(1, 0);//0为1节点的父节点,这里可以把1看作根节点,0是想象出来的一个节点
	dfs2(1, 1);//1节点的重链的起始可以看作就是一
	for (int i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		w[a]++; w[b]++;
		w[lca(a, b)] -= 2;
	}
	cal_sum(1, 0);
	int ans = -1;
	for (int i = 1; i <= n; i++)
	{
		if (w[i] == m)
		{
			int ID = id[{i, fa[i]}];
			ans = max(ans, ID);
		}
	}
	cout <<ans<< endl;
}
signed main()
{
	solve();
	return 0;
}

下面逐步解析代码:

dfs1(u,f),f为u节点的父节点,dfs1遍历整颗树,将树中的节点分为重节点(该节点的子节点中子节点的节点树最多的点)以及该节点的深度,及其父亲节点。

dfs2(u,t),根据得到的重节点将树分为不相交的重链。并记录每条重链上节点的起始,赋值给每一个同一重链上的节点。

cal就是查找共同祖先了。

calsum就是更新所有点的点权,当点权跟题目给出的对数相等时,这个点就是为我们需要的点了。因为题目给出的点肯定有公共祖先,不然题目会提示找不到应该如何输出。具体操作可以看b站视频

这里没看懂的可以去看树链剖分

其他的代码就很简单了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值