「JOISC 2020」有趣的 Joitter 交友

传送门

考虑转化为图的模型,不难发现题目要求的就是总边数。

我们定义两种边:

  1. 两个人单向关注:用单向边相连;
  2. 两个人互相关注:用双向边相连。

不难发现一个联通块内如果全都由双向边相连,那么就会自动连成一个完全图,它的贡献就是 \(siz(siz-1)\)

于是我们考虑维护具有这样性质的联通块,我们用 \(in_i\) 维护向该联通块有单向边的点集, \(out_i\) 维护该联通块由单向边连出的联通块,并记录由那个点连出的。

那么我们就很好维护了,加入一条边时,讨论一下这两个联通块是否会合并成大联通块(启发式合并),递归处理并不断更新答案。

参考代码:

#include <algorithm>
#include <cstdio>
#include <vector>
#include <set>
using namespace std;

template < class T > void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while ('0' > c || c > '9') f |= c == '-', c = getchar();
    while ('0' <= c && c <= '9') s = s * 10 + c - 48, c = getchar();
    s = f ? -s : s;
}

typedef long long LL;
const int _ = 1e5 + 5;

int n, m, fa[_], siz[_]; LL ans;
set < int > in[_];
set < pair < int, int > > out[_];

int Find(int x) { return fa[x] == x ? x : fa[x] = Find(fa[x]); }

void Add_edge(int u, int v) {
    int fu = Find(u), fv = Find(v);
    if (fu == fv || in[fv].find(u) != in[fv].end()) return ;
    auto it = out[fv].lower_bound({ fu, 0 });
    if (it == out[fv].end() || it -> first != fu) {
        ans += siz[fv], in[fv].insert(u), out[fu].insert({ fv, u }); return ;
    }
    vector < int > tin;
    vector < pair < int, int > > tout;
    if (in[fu].size() + out[fu].size() < in[fv].size() + out[fv].size()) swap(fu, fv), swap(u, v);
    for (auto i : out[fv]) {
        in[i.first].erase(i.second);
        ans -= siz[i.first];
        if (i.first != fu) tout.push_back(i);
    }
    out[fv].clear();
    ans += 2ll * siz[fu] * siz[fv] + 1ll * in[fu].size() * siz[fv] - 1ll * in[fv].size() * siz[fv];
    for (auto i : in[fv]) {
        int fi = Find(i);
        out[fi].erase({ fv, i });
        if (fi != fu) tin.push_back(i);
    }
    in[fv].clear();
    fa[fv] = fu, siz[fu] += siz[fv];
    for (auto i : tin) Add_edge(i, fu);
    for (auto i : tout) Add_edge(i.second, i.first);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("cpp.in", "r", stdin), freopen("cpp.out", "w", stdout);
#endif
    read(n), read(m);
    for (int i = 1; i <= n; ++i) fa[i] = i, siz[i] = 1;
    for (int u, v, i = 1; i <= m; ++i)
        read(u), read(v), Add_edge(u, v), printf("%lld\n", ans);
    return 0;
}
posted @ 2020-06-05 19:48  Sangber  阅读(362)  评论(0编辑  收藏  举报