题目链接:https://codeforces.com/gym/101741/problem/C
题目大意:
给你一棵含n个点树,给你一些树上的路径(起点和终点的标号),要你找一个集合,使得每一条给出的路径上至少有一点在这个集合里,而且此集合要最小。
解题思路:
不妨给每个点分配一个集合,如果此点是某路径的端点,就在集合中记录路径标号。路径信息在随着dfs的回溯过程向上传递,直到有相邻两点的路径信息有交集,更新答案,清空计入答案的点的路径信息。实际上此过程类似于找最近公共祖先并且贪心得到答案。时间复杂度不太好分析。
代码如下:
# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
vector <int> G[maxn];
set <int> s[maxn];
bool ans[maxn];
int n, m, x, y, tot;
void dfs(int v, int fa){ //fa是x的父亲
for(int i = 0; i < G[v].size(); ++i){
int w = G[v][i];
if(w != fa){
dfs(w, v);
if(s[v].size() < s[w].size()) swap(s[v], s[w]); //不swap答案不错,但是会超时...,,因为下面遍历s[w]...
for(auto j : s[w]){
if(s[v].find(j) != s[v].end()){ //有一条路径经过x和y
ans[v] = true; //优先选上面的点,祖先
break;
}else{
s[v].insert(j); //否则路径信息向上转移,目的是为了让路径两端的信息碰到一起,和为一点,有点像压缩路径
}
}
}
}
if(ans[v]){
tot++;
s[v].clear();
//如果x点计入答案,那么x点路径信息就没用了,因为其他路径必然经过x点,
//没必要再考察经过x点的其他路径了,如果路径两端的压缩不到一起,答案不会+1
}
}
void init(){
for(int i = 0; i <= n; ++i){
G[i].clear();
s[i].clear();
}
memset(ans, 0, sizeof(ans));
tot = 0;
}
int main(){
std::ios::sync_with_stdio(false);
while(cin >> n){
for(int i = 0; i < n-1; ++i){
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
cin >> m;
for(int i = 0; i < m; ++i){
cin >> x >> y;
if(x == y) ans[x] = true; //注意路径压缩不能处理单点情况!必须特判
else{
s[x].insert(i); //set, i是路径标号, 标记这个点在什么路径之中
s[y].insert(i);
}
}
dfs(1, 0);
cout << tot << endl;
for(int i = 1; i <= n; ++i)
if(ans[i])
cout << i << endl;
init();
}
return 0;
}