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
");
		else printf("%d
", 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;
}
原文地址:https://www.cnblogs.com/PCCCCC/p/13441702.html