2020牛客暑期多校训练营(第八场)A All-Star Game
题解:其实这题的本质就是维护连通块个数。
线段树维护可撤销并查集,我们以每次修改作为时间,记录条边出现的时间段,并且用线段树维护这些时间段,线段树的每个结点开一个 \(vector\) 数组, 每个结点代表一个时间区间,若边 \((u, v)\) 在节点 \(rt\) 对应的时间段存在,则 \(vec[rt].push\_back(id[{u, v}])\) 。具体代码如下
void Update(int le, int ri, int L, int R, int id, int rt){
if(L <= le && ri <= R){
vec[rt].push_back(id);
return ;
}
int mid = (le + ri) >> 1;
if(L <= mid) Update(le, mid, L, R, id, lc);
if(R > mid) Update(mid + 1, ri, L, R, id, rc);
}
然后讲一讲可撤销并查集,首先可撤销并查集不可以路径压缩,所以我们要按秩合并,当我们添加一条边 \((u, v)\) ,设 \(fau\) 为 \(u\) 的祖先,\(fav\) 为 \(v\) 的祖先,我们记录 \(fau, fav, rank[fau], rank[fav]\) ,用一个栈保存, 之后每次撤销一条边时,我们只要将 \(fau, fav\) 的属性还原就好了,代码如下
void stkpop(){
fa[stk[top].fau] = stk[top].fau;
fa[stk[top].fav] = stk[top].fav;
Rank[stk[top].fau] = stk[top].rku;
Rank[stk[top].fav] = stk[top].rkv;
sum = stk[top].sum, ans = stk[top].ans; // 这两个后面会讲
top--;
}
然后具体来讲讲怎么实现,我们从线段树的根节点开始遍历,每次优先遍历左儿子,当访问到一个节点时,我们遍历该节点的 \(vector\) ,讲所有边加入图中,并用并查集维护,若粉丝 \(v\) 之前没有偶像且偶像 \(u\) 之前也没有粉丝,让 \(ans = ans + 1\) , \(ans\) 代表最后我们所需要的偶像的个数,若粉丝 \(v\) 之前有偶像 \(vv\) 且偶像 \(u\) 之前也有其他粉丝,则 \(ans = ans - 1\) , 代码如下
int unit(int x, int y){
top++; // 记录未加入该边时图的状态
x = Find(x), y = Find(y);
stk[top].fau = x, stk[top].fav = y;
stk[top].rku = Rank[x], stk[top].rkv = Rank[y];
stk[top].sum = sum, stk[top].ans = ans;
if(x == y) return 0; // 若已子啊一个连通块中则是无效合并
if(x <= n && y > n) {
sum++;
if(Rank[x] == 1) ans++; // 偶像 x 没有粉丝
}
else {
if(Rank[x] != 1 && Rank[y] != 1){ //偶像 x,y 都已经有粉丝,这里的x, y已经是 find 过了的,所以代表的都是偶像
ans--;
}
}
if(Rank[x] < Rank[y]) swap(x, y);
if(Rank[x] == Rank[y]) Rank[x]++;
fa[y] = x;
return 1;
}
以下是AC代码
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define lc (rt << 1)
#define rc ((rt << 1) | 1)
typedef long long LL;
typedef pair<int, int> pii;
const int maxn = 5e5 + 50;
int n, m, q;
vector<int> vec[maxn << 2];
void Update(int le, int ri, int L, int R, int id, int rt){
if(L <= le && ri <= R){
vec[rt].push_back(id);
return ;
}
int mid = (le + ri) >> 1;
if(L <= mid) Update(le, mid, L, R, id, lc);
if(R > mid) Update(mid + 1, ri, L, R, id, rc);
}
map<pii, int> mp;
struct Edge
{
int u, v;
} edge[maxn * 10];
int tot = 0;
int le[maxn * 10], ri[maxn * 10];
int ans = 0, sum = 0;
struct qnode
{
int fau, fav, rku, rkv, sum, ans;
} stk[maxn * 10];
int top;
int fa[maxn], Rank[maxn];
int Find(int x){
if(x == fa[x]) return x;
return Find(fa[x]);
}
int unit(int x, int y){
top++;
x = Find(x), y = Find(y);
stk[top].fau = x, stk[top].fav = y;
stk[top].rku = Rank[x], stk[top].rkv = Rank[y];
stk[top].sum = sum, stk[top].ans = ans;
if(x == y) return 0;
if(x <= n && y > n) {
sum++;
if(Rank[x] == 1) ans++;
}
else {
if(Rank[x] != 1 && Rank[y] != 1){
ans--;
}
}
if(Rank[x] < Rank[y]) swap(x, y);
if(Rank[x] == Rank[y]) Rank[x]++;
fa[y] = x;
return 1;
}
void stkpop(){
fa[stk[top].fau] = stk[top].fau;
fa[stk[top].fav] = stk[top].fav;
Rank[stk[top].fau] = stk[top].rku;
Rank[stk[top].fav] = stk[top].rkv;
sum = stk[top].sum, ans = stk[top].ans;
top--;
}
void Query(int le, int ri, int rt){
int len = vec[rt].size();
for(int i = 0; i < len; i++){
int id = vec[rt][i];
int u = edge[id].u, v = edge[id].v;
unit(u, v);
}
if(le == ri){
if(sum != m) printf("-1\n");
else printf("%d\n", ans);
} else {
int mid = (le + ri) >> 1;
Query(le, mid, lc);
Query(mid + 1, ri ,rc);
}
while(len--){
stkpop();
}
}
int main(int argc, char const *argv[])
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n + m; i++) {
Rank[i] = 1;
fa[i] = i;
}
for(int i = 1; i <= n; i++){
int k;
scanf("%d", &k);
while(k--){
int u = i;
int v;
scanf("%d", &v);
v += n;
if(!mp.count({u, v})) {
mp[{u, v}] = ++tot;
edge[tot] = {u, v};
}
le[tot] = 1; // 记录起点
}
}
for(int i = 1; i <= q; i++){
int u, v;
scanf("%d%d", &v, &u);
v += n;
if(!mp.count({u, v})) {
mp[{u, v}] = ++tot;
edge[tot] = {u, v};
}
int id = mp[{u, v}];
if(le[id] == 0) le[id] = i; // 如果这条边未记录过,记录左端点
else { // 否则在线段树上记录该区间
ri[id] = i - 1;
if(i != 1){
Update(1, q, le[id], ri[id], id, 1);
}
le[id] = ri[id] = 0;
}
}
for(int i = 1; i <= tot; i++){
if(le[i]){
ri[i] = q;
Update(1, q, le[i], ri[i], i, 1);
}
}
Query(1, q, 1);
return 0;
}