即有撤销操作的时间分治
多次询问,每次询问可以有一种操作,可以撤回这种操作
若操作容易维护,但撤回操作不好弄,就可以离线下来
将询问看做线段树的叶子节点,一次操作就是只在一段时间内有效
因此就可以将这些操作按时间轴来区间覆盖,维护信息
然后在线段树上 d f s dfs dfs,进入节点时进行操作,离开时栈序撤销,到叶子就查询
牛客第八场 E Explorer —— 可撤销并查集 + 线段树
题目链接:点我啊╭(╯^╰)╮
n n n 个球员, m m m 个球迷,一个球员有多个球迷
球迷i喜欢看球员j的比赛,需满足一下条件之一:
①: i i i 是 j j j 的球迷
②:存在球迷 i ′ i' i′,球员 j ′ j' j′, i i i 和 j j j 都是 j ′ j' j′ 的球迷,且 j j j 是 i i i 的球迷
选择最少的球员比赛,使得所有球迷都有喜欢看的球员在比赛
每次操作对球迷 i i i 和球员 j j j 加边或删边,求满足上述条件的最少球员
根据题目的两个条件,答案可以转化为:
求所有球员的连通分量个数 − - − 独立的球员数量
若存在独立的球迷,则答案为 − 1 -1 −1
因此题目转化为了加边和删边,维护连通分量个数
那么这就可以用线段树分治来处理
设 n + m n+m n+m 总的连通分量个数为 c n t cnt cnt , n n n 个球员中独立的个体数量为 c n t a cnta cnta, m m m 个球迷中独立的个体数量为 c n t b cntb cntb
然后将操作(即查询)离线,每次查询为线段树的一个叶子节点
一条边的存在时间为 ( l , r ) (l, r) (l,r) ,将其区间覆盖到线段树上,表示这条边会影响这些查询
然后就可以在线段树上 d f s dfs dfs,用可撤销并查集来维护
进入一个节点,就将这个节点内的所有边用并查集连上,离开时再断开
每个叶子节点的答案就是当前 c n t − c n t a cnt - cnta cnt−cnta
#include
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair pii;
const int maxn = 4e5 + 5;
int n, m, q, cnt, cnta, cntb;
int f[maxn], sz[maxn], ans[maxn];
map mp[maxn];
int getf(int x) {
return x == f[x] ? x : getf(f[x]);
}
struct edge {
int u, v;
} ;
struct Rec {
int u, v;
int szu, szv;
int cnt, cnta, cntb;
};
struct Tree {
vector e;
vector rec;
} t[maxn<<2];
void build(int l, int r, int rt) {
t[rt].e.clear(), t[rt].rec.clear();
if(l == r) return;
int mid = l + r >> 1;
build(l, mid, rt<<1);
build(mid+1, r, rt<<1|1);
}
void update(int L, int R, edge ed, int l, int r, int rt) {
if(l>R || r=L && r<=R) {
t[rt].e.push_back(ed);
return;
}
int mid = l + r >> 1;
update(L, R, ed, l, mid, rt<<1);
update(L, R, ed, mid+1, r, rt<<1|1);
}
void dfs(int l, int r, int rt) {
int len = 0; Rec tmp;
for(auto i : t[rt].e) {
int u = i.u, v = i.v;
int fu = getf(u), fv = getf(v);
if(fu == fv) continue;
tmp.cnt = cnt, tmp.cnta = cnta, tmp.cntb = cntb;
cnt--;
if(sz[fu] == 1) cnta--;
if(sz[fv] == 1) cntb--;
if(sz[fu] < sz[fv]) swap(fu, fv);
tmp.u = fu, tmp.v = fv;
tmp.szu = sz[fu], tmp.szv = sz[fv];
f[fv] = fu, sz[fu] += sz[fv];
t[rt].rec.push_back(tmp); len++;
}
if(l == r) {
if(cntb != 0) ans[l] = -1;
else ans[l] = cnt - cnta;
} else {
int mid = l + r >> 1;
dfs(l, mid, rt<<1);
dfs(mid+1, r, rt<<1|1);
}
for(int i=len-1; ~i; i--) {
tmp = t[rt].rec[i];
f[tmp.u] = tmp.u, f[tmp.v] = tmp.v;
sz[tmp.u] = tmp.szu, sz[tmp.v] = tmp.szv;
cnt = tmp.cnt, cnta = tmp.cnta, cntb = tmp.cntb;
}
}
signed main() {
scanf("%d%d%d", &n, &m, &q);
build(1, q+1, 1);
for(int i=1; i<=n+m; i++) f[i] = i, sz[i] = 1;
cnt = n + m, cnta = n, cntb = m;
for(int i=1, k, x; i<=n; i++) {
scanf("%d", &k);
while(k--) {
scanf("%d", &x);
mp[i][x] = 1;
}
}
for(int i=2, u, v; i<=q+1; i++) {
scanf("%d%d", &v, &u);
if(mp[u][v] == 0) mp[u][v] = i;
else {
update(mp[u][v], i-1, {u, v+n}, 1, q+1, 1);
mp[u][v] = 0;
}
}
for(int i=1; i<=n; i++)
for(auto j : mp[i]) {
if(j.second == 0) continue;
int u = i, v = j.first;
update(j.second, q+1, {u, v+n}, 1, q+1, 1);
}
dfs(1, q+1, 1);
for(int i=2; i<=q+1; i++) printf("%d\n", ans[i]);
}