牛客第八场 A All-Star Game —— 线段树分治 + 可撤销并查集

线段树分治

即有撤销操作的时间分治
多次询问,每次询问可以有一种操作,可以撤回这种操作
若操作容易维护,但撤回操作不好弄,就可以离线下来
将询问看做线段树的叶子节点,一次操作就是只在一段时间内有效
因此就可以将这些操作按时间轴来区间覆盖,维护信息
然后在线段树上 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 cntcnta

#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]); 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值