Codeforces 1444C 可撤销并查集

题意

传送门 Codeforces 1444C Team-Building

题解

图中节点划分为不相交的点集 S S S,求满足所构成子图为二分图的点集对 ( S 1 , S 2 ) (S_1,S_2) (S1,S2) 数量。

若点集 S S S 构成的子图存在奇环,则它与任意其他点集构成的子图都不可能是二分图。讨论满足二分图判定的点集即可。

若两点间存在一条边,则两点划分至不同的集合,可以通过并查集维护这样的关系。依次考虑点集 S S S,处理与它存在连边的点集 T T T,判断 S , T S,T S,T 构成的子图是否为二分图即可。可以用可撤销并查集维护这样的关系。

总时间复杂度 O ( m log ⁡ n ) O(m\log n) O(mlogn)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MAXN = 5E5 + 5;
int N, M, K, C[MAXN];
struct edge
{
    int u, v, id;
};
vector<edge> es[MAXN], es2[MAXN];

struct DSU
{
    int par[MAXN * 2], rnk[MAXN * 2], n;
    int stk[MAXN * 2][2], top;
    void init(int _n)
    {
        n = _n, top = 0;
        for (int i = 0; i < n; ++i)
            par[i] = i, rnk[i] = 0;
    }
    int find(int x) { return par[x] == x ? x : find(par[x]); }
    bool same(int x, int y) { return find(x) == find(y); }
    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x == y)
            return;
        if (rnk[x] > rnk[y])
            swap(x, y);
        int d = rnk[x] == rnk[y];
        stk[top][0] = x, stk[top++][1] = d;
        par[x] = y, rnk[y] += d;
    }
    void undo(int t)
    {
        while (top > t)
        {
            --top;
            int x = stk[top][0], d = stk[top][1];
            rnk[par[x]] -= d, par[x] = x;
        }
    }
} dsu;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> N >> M >> K;
    for (int i = 0; i < N; ++i)
        cin >> C[i], --C[i];
    for (int i = 0; i < M; ++i)
    {
        int u, v;
        cin >> u >> v;
        --u, --v;
        if (C[u] == C[v])
            es[C[u]].push_back({u, v, C[v]});
        else
        {
            if (C[u] < C[v])
                swap(u, v);
            es2[C[u]].push_back({u, v, C[v]});
        }
    }
    
    ll res = 0;
    dsu.init(2 * N);
    set<int> st;
    for (int i = 0; i < K; ++i)
    {
        bool ok = 1;
        for (auto &e : es[i])
        {
            int u = e.u, v = e.v;
            dsu.merge(u, N + v), dsu.merge(N + u, v);
            if (dsu.same(u, v) || dsu.same(N + u, N + v))
                ok = 0;
        }
        if (ok == 0)
            continue;
        set<int> st2;
        sort(es2[i].begin(), es2[i].end(), [](const edge &x, const edge &y)
             { return x.id < y.id; });
        for (int l = 0, r = 0, n = es2[i].size();l < n;)
        {
            while (r < n && es2[i][l].id == es2[i][r].id)
                ++r;
            int bg = dsu.top, g = es2[i][l].id;
            bool ok = 1;
            for (int j = l; j < r; ++j)
            {
                int u = es2[i][j].u, v = es2[i][j].v;
                dsu.merge(u, N + v), dsu.merge(N + u, v);
                if (dsu.same(u, v) || dsu.same(N + u, N + v))
                    ok = 0;
            }
            if (ok == 0)
                if (st.count(g))
                    st.erase(g), st2.insert(g);
            dsu.undo(bg);
            l = r;
        }
        res += st.size();
        for (int g : st2)
            st.insert(g);
        st.insert(i);
    }
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值