CF 1444C Team-Building 可撤销并查集+种类并查集判二分图

文章目录

题意

给定n个点,m条边,k个集合。每个点都属于自己的集合,每次取任意两个集合构成的子图,问是否是二分图,最后求二分子图的个数。

分析

根据题目的数据范围,暴力枚举子图加dfs判二分图是肯定会超时的,因此考虑另一种判二分图的方法,种类并查集。

种类并查集判二分图的方法:假设x和y分别处于二分图的两部分,那么我们建立两个点的虚点x+n和y+n代表对立面的点,因此从x和y+n是处于一个集合中的,y和x+n同理。最后如果发现x和y在一个集合中,说明存在奇环即不是二分图。

然后我们考虑两个集合中的点是怎么产生贡献的

  1. 如果同一个集合中的点就已经产生奇环,那么无论是哪个集合和它相连一定不存在贡献
  2. 两个集合中的点构成奇环

第一种情况我们可以直接处理,先把相邻的同集合元素相连,如果产生奇环就标记该集合。
然后我们再去给边分类。我们将所有连着相同两个集合的边分为一类,就可以枚举所有的情况了。
最后可以通过未标记的集合个数来推算出总的枚举可能,然后减去不符合的情况就可以得出答案了。

代码

#include <bits/stdc++.h>
#define ACM_LOCAL
#define fi first
#define se second
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e6 + 10, M = 5e5 + 10, INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n, m, k, cnt;
int c[N], pd[N];
map<PII, int> mp;
vector<int> vec[N];
struct Edge {
    int u, v;
}e[N];
struct Undo_dsu {
    stack<PII> st;
    int fa[N], siz[N];
    void init() {
        while (st.size()) st.pop();
        for (int i = 1; i <= n*2; i++) fa[i] = i, siz[i] = 1;
    }
    int find(int x) {return fa[x] == x ? x : find(fa[x]);}
    void merge(int x, int y) {
        int fx = find(x), fy = find(y);
        if (fx == fy) return;
        if (siz[fx] > siz[fy]) swap(fx, fy), swap(x, y);
        siz[fy] += siz[fx], fa[fx] = fy;
        st.push({fx, fy});
    }
    void undo() {
        PII now = st.top();
        fa[now.fi] = now.fi;
        siz[now.se] -= siz[now.fi];
        st.pop();
    }
}dsu;

void solve() {
    cin >> n >> m >> k; dsu.init(); ll ans = 0;
    for (int i = 1; i <= n; i++) cin >> c[i];
    for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v;
    for (int i = 1; i <= m; i++) {
        int u = e[i].u, v = e[i].v;
        if (c[u] == c[v]) {
            int fu = dsu.find(u), fv = dsu.find(v);
            if (fu == fv) pd[c[u]] = 1;
            else dsu.merge(u, v+n), dsu.merge(v, u+n);
        }
    }
    for (int i = 1; i <= m; i++) {
        int u = e[i].u, v = e[i].v;
        if (c[u] == c[v] || pd[c[u]] || pd[c[v]]) continue;
        if (c[u] > c[v]) swap(u, v);
        if (!mp[{c[u], c[v]}]) mp[{c[u], c[v]}] = ++cnt;
        vec[mp[{c[u], c[v]}]].pb(i);
    }
    for (auto item : mp) {
        int id = item.se;
        int now = dsu.st.size(), f = 0;
        for (auto it : vec[id]) {
            int u = e[it].u, v = e[it].v;
            int fu = dsu.find(u), fv = dsu.find(v);
            if (fu == fv) {
                f = 1; break;
            } else {
                dsu.merge(u, v+n);
                dsu.merge(v, u+n);
            }
        }
        if (f) ans--;
        while (dsu.st.size() > now) dsu.undo();
    }
    int left = 0;
    for (int i = 1; i <= k; i++) if (!pd[i]) left++;
    ans += 1ll*(left-1)*left/2;
    printf("%lld\n", ans);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
#endif
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值