【CodeForces】 Codeforces Round #680 Div1 C,Div2 E,——Team-Building(并查集套种类并查集)

题目链接
第一次打div1,失误太多……

题目大意

有一个无向图,图中的每个点都有类型。且有的类型可能没有点。现在选出两种类型,保证这两种类型的所有的点组成的子图中不存在奇数环

分析

首先是类型的个数,根据题意,最多可能可以选出 k ∗ ( k − 1 ) / 2 = 124 , 999 , 750 , 000 k * (k - 1) / 2 = 124,999,750,000 k(k1)/2=124,999,750,000 种可能。即使能O(1)判环也不太行。
所以反过来,找哪些组合会出现奇数环,然后拿组合数减去可能的组合,即可得到答案。

但是仍然不能枚举每种可能。

再阅读题面发现,这是一个很稀疏的图,边数只有 500000 500000 500000 条边,也就是说,最多只有 500000 500000 500000 种组合会出现有边连接的情况。

那么我们可以枚举这些边,然后通过染色来解决奇数环问题。

但是枚举边仍然不是一个很好的办法,因为有些边左右两端的类型相同,也有不少边,他们的关系组合也是相同的,如果枚举边容易出现错解的情况,同时效率较低。

我选择使用hash的办法,讲所有可能的组合记录下来,然后把这些组合涉及到的那些边也记录下来。然后遍历所有这些组合,然后暴力染色去判奇数环。

但是可能会出现如下的数据,能成功把这个方法T掉:有一半的点属于同一组,另外一半的点分别属于不同的组,然后这些组之间都有连线。那么就会出现 n 2 \frac{n}{2} 2n的点被遍历了 n 2 \frac{n}{2} 2n次。那么复杂度仍然可以是 O ( n 2 ) O(n^2) O(n2)

继续简化,我们将属于相同类别且相邻的点放进同一个 group 中,这可以用普通的并查集实现。然后对于属于同一并查集的点,我们对其进行染黑白色。原则是相邻的点染不同的色。如果不知道染什么,则随意染色。

这时候会出现,在单一类别中,也会出现奇数环的情况,这时候需要标记一下这些类别即可,也不需要继续对这种类别继续染色。

那么我们可以将这些 group 认为是一个新的点。

接下来还是继续遍历上面 hash 得到的边

那么问题就变成了,两侧的 group 已经分别染色完成了,但是在逻辑上,任意一侧的 group 的颜色可以同时翻转,这就取决于这两个 group 的连接处的染色是否相同。那么我们可以将 group 分类为两种:反转和不反转,也就是种类并查集。

那么再用种类并查集判断是否能通过翻转颜色得到合法的子图。

注意上面本身会出现奇数环的类别,可以直接跳过这种组合,然后在最后将其删除。

AC code

#include <bits/stdc++.h>

using namespace std;

void solve() {
    
    
    long long n, m, k;
    cin >> n >> m >> k;
    vector<vector<int>> edge(n + 1);
    vector<int> type(n + 1), color(n + 1, -1), f(n + 1);
    vector<bool> fail(k + 1, false);
    vector<vector<pair<int, int>>> pa;
    map<pair<int, int>, int> connectHash;
    for (int i = 0; i < f.size(); ++i) f[i] = i;
    function<int(int)> finds = [&](int x) {
    
     return x == f[x] ? x : f[x] = finds(f[x]); };

    for (int i = 0; i < n; ++i) cin >> type[i + 1];
    for (int i = 0; i < m; ++i) {
    
    
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
        int tu = type[u], tv = type[v];
        if (tu > tv) swap(tu, tv);
        if (tu == tv) {
    
    
            int ru = finds(u), rv = finds(v);
            if (ru == rv) continue;
            f[ru] = rv;
            continue;
        }
        auto iter = connectHash.find({
    
    tu, tv});
        int pos = 0;
        if (iter == connectHash.end()) {
    
    
            pos = connectHash.size();
            connectHash.insert({
    
    {
    
    tu, tv}, connectHash.size()});
            pa.emplace_back();
        } else pos = iter->second;
        pa[pos].push_back({
    
    u, v});
    }

    for (int i = 1; i <= n; ++i) {
    
    
        if (color[i] != -1) continue;
        if (fail[type[i]]) continue;
        queue<int> q;
        q.push(i);
        color[i] = 0;

        while (!q.empty()) {
    
    
            int cur = q.front();
            q.pop();
            int cc = color[cur];
            for (auto &to : edge[cur]) {
    
    
                if (type[to] != type[cur]) continue;
                if (color[to] == -1) {
    
    
                    color[to] = cc ^ 1;
                    q.push(to);
                } else if (color[to] == cc) {
    
    
                    fail[type[i]] = true;
                    break;
                }
            }
        }
    }

    long long cnt = 0;
    for (auto &item : pa) {
    
    
        if (fail[type[item[0].first]]) continue;
        if (fail[type[item[0].second]]) continue;
        map<int, int> flag;
        for (auto &p : item) {
    
    
            int ru = finds(p.first), rv = finds(p.second);

            if (flag.count(ru) == 0) {
    
    
                flag.insert({
    
    ru, ru});
                flag.insert({
    
    ru + n, ru + n});
            }
            if (flag.count(rv) == 0) {
    
    
                flag.insert({
    
    rv, rv});
                flag.insert({
    
    rv + n, rv + n});
            }

            function<int(int)> find2 = [&](int x) {
    
    
                auto iter = flag.find(x);
                if (iter == flag.end()) return x;
                if (iter->second == x) return x;
                iter->second = find2(iter->second);
                return iter->second;
            };

            int ru1 = find2(ru), rv1 = find2(rv), ru2 = find2(ru + n), rv2 = find2(rv + n);
            if (color[p.first] == color[p.second]) {
    
    
                if (ru1 == rv1 || ru2 == rv2) {
    
    
                    cnt++;
                    break;
                }
                flag[ru1] = rv2;
                flag[ru2] = rv1;
            } else {
    
    
                if (ru1 == rv2 || ru2 == rv1) {
    
    
                    cnt++;
                    break;
                }
                flag[ru1] = rv1;
                flag[ru2] = rv2;
            }
        }
    }
    int failCnt = 0;
    for (auto flag : fail) {
    
    
        if (flag) {
    
    
            cnt += k - 1 - failCnt;
            failCnt++;
        }
    }
    cout << k * (k - 1) / 2 - cnt << endl;
}

signed main() {
    
    
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed localTestCount = 1, localReadPos = cin.tellg();
    char localTryReadChar;
    do {
    
    
        if (localTestCount > 20)
            throw runtime_error("Check the stdin!!!");
        auto startClockForDebug = clock();
        solve();
        auto endClockForDebug = clock();
        cout << "Test " << localTestCount << " successful" << endl;
        cerr << "Test " << localTestCount++ << " Run Time: "
             << double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
             cin.putback(localTryReadChar));
#else
    solve();
#endif
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_43448982/article/details/109447184