考虑转化为图的模型,不难发现题目要求的就是总边数。
我们定义两种边:
- 两个人单向关注:用单向边相连;
- 两个人互相关注:用双向边相连。
不难发现一个联通块内如果全都由双向边相连,那么就会自动连成一个完全图,它的贡献就是 \(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;
}