[并查集]刷题记录之带权并查集🔥

102 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

  1. 奇偶游戏

小 A 和小 B 在玩一个游戏。 首先,小 A 写了一个由 0 和 1 组成的序列 S,长度为 N。 然后,小 B 向小 A 提出了 M 个问题。 在每个问题中,小 B 指定两个数 l 和 r,小 A 回答 S[l∼r] 中有奇数个 1 还是偶数个 1。 机智的小 B 发现小 A 有可能在撒谎。 例如,小 A 曾经回答过 S[1∼3] 中有奇数个 1,S[4∼6] 中有偶数个 1,现在又回答 S[1∼6] 中有偶数个 1,显然这是自相矛盾的。 请你帮助小 B 检查 这 M 个答案,并指出在至少多少个回答之后可以确定小 A 一定在撒谎。 即求出一个最小的 k,使得 01 序列 S 满足第 1∼k 个回答,但不满足第1∼k+1 个回答。

输入格式

第一行包含一个整数 N,表示 01 序列长度。 第二行包含一个整数 M,表示问题数量。 接下来 MM 行,每行包含一组问答:两个整数 l 和 r,以及回答 even 或 odd,用以描述 S[l∼r] 中有偶数个 1 还是奇数个 1。

输出格式

输出一个整数 k,表示 01 序列满足第 1∼k 个回答,但不满足第 1∼k+1 个回答,如果 01 序列满足所有回答,则输出问题总数量。

数据范围

N≤109,M≤5000

输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

对于这道题提到的区间1的个数,我们首先可以想到用前缀和来存储,面对题目中的1个数奇偶性可以借助前缀和来这样表示

1.如果Shu[l~r]有偶数个1 那么在Sum[l-1]与Sum[R]的奇偶性相同;
2.如果Shu[l~r]有奇数个1 那么在Sum[l-1]与Sum[R]的奇偶性相反;

并且本题对于区间的1个数奇偶性还有传递性(下面用Q来代表区间)

 1.如果Q1与Q2奇偶性相同,Q2与Q3奇偶性相同,那么Q1和Q3的奇偶性相同;
 2.如果Q1与Q2奇偶性相同,Q2与Q3奇偶性相反,那么Q1和Q3的奇偶性相反;
 3.如果Q1与Q2奇偶性相反,Q2与Q3奇偶性相反,那么Q1和Q3的奇偶性相同;

同时捏 对于这种请求数量较少,但题目的数据较大的题目,我们可以采用离散化来优化

为了处理错综复杂的传递关系我们引入了边权并查集 本质上并查集是一种树形结构,我们有一个函数来建树,这个就不具体展开说说了

int get(int x) {
   if (!vis.count(x))vis[x] = ++cnt;
   return vis[x];
}

在主函数中调用这个函数可以很巧妙的完成离散化的任务

cin >> x >> y >> sk;
x = get(x - 1), y = get(y);

带边权并查集需要用到dis边权数组 ,它表示的含义是x到父亲fu[x]的间隔的区间段奇偶性

 1.如果dis[x]==0;代表x将与fu[x]奇偶性相同;因为(奇+偶=奇)(偶+偶=偶)
 2.如果dis[x]==1,代表x将与fu[x]奇偶性不同;因为(奇+奇=偶)(偶+奇=奇)

先对树上结点找根结点,倘若根节点相同,将两树上结点间隔区间的1数量奇偶性整理,如果奇偶性不符合输入的情况那么直接退出循环即可

倘若根节点不同那就把他俩置于同一个区间,默认是将前者祖先的父亲设置为后者的祖先,然后dis[xx]=dis[y]^dis[x]^f;

graph TD
xx --> disx --> x
yy --> disy--> y

因为现在xx的父亲是yy了 所以dis[xx]=dis[y]^(xx自带的孩子x对于路径的边权)dis[x]^f(两个祖先之间间隔的区间奇偶性)

有种老大拖家带口加入别的老大组织的感觉,并查集常常就是这样。<

最后附上完整AC代码

#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
const int N = 2e4 + 20;
int n, m,fu[N],dis[N],cnt,x,y,f;
string sk;
unordered_map<int, int>vis;
int get(int x) {
	if (!vis.count(x))vis[x] = ++cnt;
	return vis[x];
}
int find(int x) {
	if (fu[x] != x) {
		int root = find(fu[x]);
		dis[x] ^= dis[fu[x]];
		fu[x] = root;
	}return fu[x];
}	
int main() {
	cin >> n >> m;
	for (int i = 0; i < N; i++)fu[i] = i;
	int ans = m;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y >> sk;
		x = get(x - 1), y = get(y);
		(sk == "odd") ? f = 1 : f = 0;
		int xx = find(x), yy = find(y);
		if (xx == yy) {
			if (dis[x] ^ dis[y] != f) { ans = i - 1; break; }
		}
		else fu[xx] = yy, dis[xx] = dis[x] ^ dis[y] ^ f;//xx的关系就是主子y的关系加上f再加上下附结点x与自身的关系
	}cout << ans;
	return 0;
}