[Codeforces] Codeforces Round #847 (Div. 3) G. Tokens on Graph | 图论,并查集

281 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

[Codeforces] Codeforces Round #847 (Div. 3) G. Tokens on Graph | 图论,并查集

题目链接

codeforces.com/contest/179…

题目

Codeforces-847-G-题目.png

题目大意

给一张n个点m条边无向连通图,1号点为终点,n个点中有p个点上有token,并且有b个点上有bonus,bonus和token可以在同一个点上,bonus始终不变,初始状态token不会重复在同一个点上,但token可以重叠在同一个点上。现在规定你可以进行如下操作:

  1. 最开始你可以做恰好一次小操作:选择一个token移动到它所在点的任意一个相邻点处
  2. 如果之前的token移动到的点是一个bonus点,你可以选择另一个和上一个点不同的点再进行一次小操作,并重复2的判断

问进行上述操作后是否存在一个token能到达终点(1号点),如果1号点原本有token也算到达。

思路

暂时先不考虑相邻两次小操作的token需要不同的规定,即假设一个token只要在上次遇到bonus点就可以一直走下去。那要让至少一个token能走到1号点,则至少要有一个token可以通过只有bonus点作为中间点的路径到达1号点,这是一个必要条件。

我们如何判断是否存在这样的token呢?

首先排除掉特殊情况,1号点有token 和 1号点的邻近点有token

然后我们构建一个并查集,只对于两端的点都是bonus的边进行merge,这样子就有了一些连通块,同时我们维护每个连通块的bonus点数量以筛去不是由bonus点组成的单点连通块。

之后我们对1号点的所有邻近点进行判断,如果它在某个bonus连通块里,把该连通块的根节点加入集合link。我们再对所有的token点做类似操作,如果一个token点的某个邻近点所在连通块和S中对应的某个连通块一样,说明这个token点可以只经过bonus点走到1号点,把这个点加入集合candidate。

做到这里,我们可以筛选出所有能只通过bonus点到达1号点的token集合。接下来,我们考虑限制:两次小操作对应的token不能是同一个。

第一种情况:candidate.size() = 0 直接NO

第二种情况:candidate.size() > 1 这代表至少有两个token可以走到1号点,它们交替着走,到1号点距离更短的点就能先到达1号点,所以是YES

第三种情况:candidate.size() = 1 这时候只有一个初始token点可以只经过bonus点到达1号点,设这个点是u。我们要比较其他token点能给u提供的小操作次数 和 1号点只经过bonus点到达u的最短距离。后者用一个平凡的最短路算法可以求出,前者则是对于剩下的token点进行如下考虑:

  1. 如果这个token点的邻近点不存在bonus点,那么这个token点不能提供任何额外次数;

  2. 如果这个token点的邻近点存在bonus点

    • 存在一个bonus点所在的bonus连通块size > 1,那么token点可以在这个连通块里绕圈,可以提供无限的额外次数
    • 如果所有bonus点都是单点bonus连通块,那么这个token只能提供1次额外次数

我们对每个点提供的额外次数之和 + 1(u点先走,本身就有一次) 和 u点到1号点的最短距离进行比较,>= 则YES,<则NO。至此判断完毕,全题结束。

代码

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
using namespace std;
​
const int maxn = 2e6 + 10;
const int mod = 1e9 + 7;
const int inf = 1LL << 60;#define db 0
#define ttest if (db) 
​
​
​
void solve() {
    int n, m, p, b;
    cin >> n >> m;
    vector<vector<int>> edge(n);
    cin >> p >> b;
    vector<int> tokens(n, 0), bonus(n, 0);
    for (int i = 0; i < p; ++i) {int x; cin >> x; --x; tokens[x] = 1;}
    for (int i = 0; i < b; ++i) {int x; cin >> x; --x; bonus[x] = 1;}
    for (int i = 0; i < m; ++i) {
        int x, y; 
        cin >> x >> y;
        -- x; -- y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
​
    if (tokens[0]) {cout << "YES\n"; return;}
    for (int x : edge[0]) if (tokens[x]) {cout << "YES\n"; return;}
​
    vector<int> f(n), sz(bonus);
    iota(f.begin(), f.end(), 0);
    function<int(int)> fa = [&](int x) {
        return (x == f[x]) ? x : f[x] = fa(f[x]);
    };
    function merge = [&](int x, int y) {
        x =  fa(x); y = fa(y);
        f[x] = y;
        if (x != y) {
            sz[y] += sz[x];
        }
        return;
    };
​
    for (int i = 0; i < n; ++i) if (bonus[i]) {
        for (int j : edge[i]) if (bonus[j]) {
            merge(i, j);
        }
    }
​
    set<int> link;
    for (int x : edge[0]) if (bonus[x] && sz[fa(x)]) link.insert(fa(x));
    if (!link.size()) {cout << "NO\n"; return;}
​
    set<int> candidate; 
    for (int i = 0; i < n; ++i) if (tokens[i]) {
        for (int x : edge[i])  if (bonus[x] && link.count(fa(x))) {
            candidate.insert(i);
            break;
        }
    }
    if (candidate.size() > 1) {cout << "YES\n"; return;}
    if (!candidate.size()) {cout << "NO\n"; return;}
​
    assert((int)candidate.size() == 1);
    int u = *candidate.begin();
    int steps = 0;
    for (int i = 0; i < n && steps != inf; ++i) if (i != u && tokens[i]) {
        int cc = 0;
        for (int x : edge[i]) {
            if (sz[fa(x)] > 1) {
                steps = inf; break;
            }
            else if (sz[fa(x)] == 1) cc = 1;
        }
        if (steps < inf) steps += cc;
    }
    queue<int> q;
    vector<int> vis(n, 0), dis(n, inf);
    vis[0] = 1; dis[0] = 0; q.push(0);
    while (!q.empty()) {
        int x = q.front(); q.pop();
        for (int y : edge[x]) if (!vis[y] && (bonus[y] || y == u)) {
            dis[y] = dis[x] + 1;
            vis[y] = 1;
            q.push(y);
        }
    }
    assert(dis[u] != inf);
    if (dis[u] <= steps+1) cout << "YES\n"; else cout << "NO\n";
}
​
​
void refresh() {
    
}
    
​
signed main() {
    IOS
    int t;
    t = 1;
    cin >> t;  
    while (t--) {
        solve();
        refresh();
    }
    return 0;
}