CodeForces Gym 101741 C. Cover the Paths(dfs + set + 贪心)

题目链接: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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值