线段树分治
即有撤销操作的时间分治
多次询问,每次询问可以有一种操作,可以撤回这种操作
若操作容易维护,但撤回操作不好弄,就可以离线下来
将询问看做线段树的叶子节点,一次操作就是只在一段时间内有效
因此就可以将这些操作按时间轴来区间覆盖,维护信息
然后在线段树上
d
f
s
dfs
dfs,进入节点时进行操作,离开时栈序撤销,到叶子就查询
牛客第八场 E Explorer —— 可撤销并查集 + 线段树
题目链接:点我啊╭(╯^╰)╮
题目大意:
n
n
n 个球员,
m
m
m 个球迷,一个球员有多个球迷
球迷
i
i
i 喜欢看球员
j
j
j 的比赛,需满足一下条件之一:
①:
i
i
i 是
j
j
j 的球迷
②:存在球迷
i
′
i'
i′,球员
j
′
j'
j′,
i
i
i 和
i
′
i'
i′ 都是
j
′
j'
j′ 的球迷,且
i
′
i'
i′ 是
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<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 4e5 + 5;
int n, m, q, cnt, cnta, cntb;
int f[maxn], sz[maxn], ans[maxn];
map <int, int> 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 <edge> e;
vector <Rec> 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) return;
if(l>=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]);
}