题目描述:
给定一棵由 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站视频。
这里没看懂的可以去看树链剖分。
其他的代码就很简单了。