2022牛客多校第8场 I.Equivalence in Connectivity
题目大意
给定一张 \(n\) 个点 \(m\) 条边的无向图,定义两张图 \(G_1\) 和 \(G_2\) 连通性等价,当且仅当 \(\forall u,v\in G_1\),只要在 \(G_1\) 中 \(u\) 和 \(v\) 连通,一定有 \(G_2\) 中 \(u\) 和 \(v\) 连通。再给出 \(k-1\) 次操作,每次操作都会继承之前的某张图,添加或删除一条边,形成一张新图,由此得到 \(k\) 张图。求这 \(k\) 张图的连通性等价类。
题解
在原图上添加或删除一条边,形成一张新图,这一操作形成一棵树。求出这棵树的DFS序,将树转化为序列。每条边都只在DFS序上的若干段区间上存在,于是可以在DFS序上做线段树分治。如果没有撤销操作,每次加入的一条边覆盖了某一棵子树内的所有结点这一DFS序区间。观察得每添加一次撤销操作,最多只会将区间分裂成两半,即最多只会新覆盖1个区间,最终覆盖的区间数量级是\(O(m+k)\),每个区间只会横跨线段树上 \(O(\log k)\) 个区间。两张图联通性等价,实际上只要连通块相同即可。可以使用并查集维护连通块,然后再辅以某种哈希方法,来求得每张图的哈希值,就能求得最终的连通性等价类。在线段树分治中,必须是可撤销并查集,以支持回溯,因此只能按秩合并,不能路径压缩,并查集单次操作复杂度 \(O(\log n)\)。对于哈希方法,这里使用双哈希,每个结点权值赋予一个随机数,一个连通块的哈希定义为该连通块内所有结点权值的异或和,这样方便撤销。整张图的哈希值定义为所有连通块的哈希值之和。最终时间复杂度为 \(O((m+k)\log k\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn = 100005;
struct Graph {
struct edge { int Next, to; };
edge G[200010];
int head[100010];
int cnt;
Graph() :cnt(2) {}
void clear(int n) {
cnt = 2;fill(head, head + n + 2, 0);
}
void add_edge(int u, int v) {
G[cnt].to = v;
G[cnt].Next = head[u];
head[u] = cnt++;
}
};
struct UFS {
int f[maxn], rk[maxn];
pair<LL, LL> val[maxn];
stack<pair<int, int>> opt;
pair<LL, LL> hashval;
int find(int u) {
while (u ^ f[u]) u = f[u];
return u;
}
void merge(int u, int v) {
if ((u = find(u)) == (v = find(v))) return;
if (rk[u] > rk[v]) swap(u, v);
opt.push(make_pair(u, rk[u] == rk[v]));
f[u] = v; rk[v] += (rk[u] == rk[v]);
hashval.first -= val[u].first + val[v].first;
hashval.second -= val[u].second + val[v].second;
val[v].first ^= val[u].first;
val[v].second ^= val[u].second;
hashval.first += val[v].first;
hashval.second += val[v].second;
}
void undo() {
int u = opt.top().first, v = f[u];
rk[v] -= opt.top().second;
hashval.first -= val[v].first;
hashval.second -= val[v].second;
val[v].first ^= val[u].first;
val[v].second ^= val[u].second;
hashval.first += val[u].first + val[v].first;
hashval.second += val[u].second + val[v].second;
f[u] = u; opt.pop();
}
};
UFS S;
Graph G;
map<pair<int, int>, int> mp;
pair<int, int> edges[maxn << 1];
set<pair<int, int>> s[maxn << 1];
vector <pair<int, int>> T[maxn << 2];
struct opt { int o, u, v; }opts[maxn];
int dfn[maxn], dfn_t[maxn], sz[maxn];
int t, k, n, m, dfn_index;
void DFS(int u, int fa) {
dfn[u] = ++dfn_index;
dfn_t[dfn[u]] = u;
sz[u] = 1;
for (int i = G.head[u];i;i = G.G[i].Next) {
int v = G.G[i].to;
if (v == fa) continue;;
DFS(v, u);
sz[u] += sz[v];
}
}
void build(int rt, int L, int R) {
T[rt].clear();
if (L == R) return;
int mid = (L + R) >> 1;
build(rt << 1, L, mid);
build(rt << 1 | 1, mid + 1, R);
}
void insert(int rt, int L, int R, int QL, int QR, pair<int, int> x) {
if (R < QL || QR < L) return;
if (QL <= L && R <= QR) { T[rt].push_back(x); return; }
int mid = (L + R) >> 1;
insert(rt << 1, L, mid, QL, QR, x);
insert(rt << 1 | 1, mid + 1, R, QL, QR, x);
}
pair<LL, LL> hashval[maxn];
pair<LL, LL> val[maxn];
void DFS(int rt, int L, int R) {
int sz = S.opt.size();
pair<LL, LL> curval = S.hashval;
for (auto x : T[rt])
S.merge(x.first, x.second);
if (L == R) {
hashval[dfn_t[L]] = S.hashval;
while (S.opt.size() != sz) S.undo();
return;
}
int mid = (L + R) >> 1;
DFS(rt << 1, L, mid);
DFS(rt << 1 | 1, mid + 1, R);
while (S.opt.size() != sz) S.undo();
}
vector<int> group[maxn];
map<pair<LL, LL>, int> mp2;
void get_ans() {
int idx = 0; mp2.clear();
for (int i = 1;i <= k;++i) {
int id = 0;
if (!mp2.count(hashval[i])) { mp2[hashval[i]] = ++idx; id = idx; group[idx].clear(); }
else id = mp2[hashval[i]];
group[id].push_back(i);
}
printf("%d\n", idx);
for (int i = 1;i <= idx;++i) {
printf("%d", group[i].size());
for (auto x : group[i])
printf(" %d", x);
printf("\n");
}
}
int main() {
mt19937 gen(chrono::system_clock::now().time_since_epoch().count());
uniform_int_distribution<LL> dis(1, 1e9);
for (int i = 1;i <= 100000;++i)
val[i] = make_pair(dis(gen), dis(gen));
scanf("%d", &t);
while (t--) {
scanf("%d%d%d", &k, &n, &m);
mp.clear(); G.clear(k);
dfn_index = 0;
int index = 0;
S.hashval = make_pair(0LL, 0LL);
for (int i = 1;i <= n;++i) {
S.f[i] = i;
S.rk[i] = 0;
S.val[i] = val[i];
S.hashval.first += val[i].first;
S.hashval.second += val[i].second;
}
while (!S.opt.empty()) S.opt.pop();
for (int i = 1;i <= m;++i) {
int u, v;
scanf("%d%d", &u, &v);
if (u > v) swap(u, v);
mp[make_pair(u, v)] = ++index;
edges[index].first = u;
edges[index].second = v;
s[index].clear();
s[index].insert(make_pair(1, k));
}
char buf[10];
for (int i = 2;i <= k;++i) {
int p, u, v;
scanf("%d%s%d%d", &p, buf, &u, &v);
if (u > v) swap(u, v);
if (buf[0] == 'a') opts[i].o = 1;
else opts[i].o = 0;
opts[i].u = u; opts[i].v = v;
G.add_edge(p, i);
G.add_edge(i, p);
}
DFS(1, 0);
for (int i = 2;i <= k;++i) {
int u = opts[i].u, v = opts[i].v;
int id = 0;
if (mp.count(make_pair(u, v))) id = mp[make_pair(u, v)];
else {
mp[make_pair(u, v)] = id = ++index; s[index].clear();
edges[index].first = u;
edges[index].second = v;
}
if (opts[i].o) { // add
s[id].insert(make_pair(dfn[i], dfn[i] + sz[i] - 1));
}
else { // remove
auto it = s[id].lower_bound(make_pair(dfn[i], maxn)); --it;
int l = it->first, r = it->second;
s[id].erase(it);
if (l <= dfn[i] - 1) s[id].insert(make_pair(l, dfn[i] - 1));
if (dfn[i] + sz[i] <= r) s[id].insert(make_pair(dfn[i] + sz[i], r));
}
}
build(1, 1, k);
for (int i = 1;i <= index;++i)
for (auto x : s[i])
insert(1, 1, k, x.first, x.second, edges[i]);
DFS(1, 1, k);
get_ans();
}
return 0;
}