The Number of Imposters(cf)并查集

对并查集小白来说,实在是很难

特别是并查集里用的路径压缩的递归还是比较令人窒息的

看了题解,之前看了各个大佬写的都没有看懂

后来看博客看视频补了并查集的知识点

才算是搞清楚了点

这个大佬写的超级好:

Codeforces1594D The Number of Imposters (并查集)_Fau的博客-CSDN博客

接下来的代码就是引用的这位大佬的,然后我做了很多批注,方便理解学习和之后的复习

#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define rrep(i, n) for (int i = m; i >= (1); --i)
typedef long long ll;

const int N = 2E5 + 10;
int p[N], dis[N]; int cou[N][2]; //节点N中0/1的数量
int find(int x) { //带权并查集
	if (x != p[x]) {
		int root = find(p[x]);
		dis[x] ^= dis[p[x]]; //偶 + 偶 == 偶|偶 + 奇 == 奇|奇 + 偶 == 奇|奇 + 奇 == 偶 对应2进制的异或^
		p[x] = root; //因为奇偶只用看最右边那位
	}
	return p[x];
}
int main()
{
	int t; cin >> t;
	while (t--) {
		int n, m; scanf("%d %d", &n, &m);
		rep(i, n) p[i] = i, dis[i] = 0, cou[i][1] = 0, cou[i][0] = 1; //先默认每个点都是0(本身每个点原始离根(自己)为0)
		bool flag = 1; //如果flag最后为0,说明有矛盾
		rep(i, m) {
			int a, b; char c[10]; scanf("%d %d %s", &a, &b, c); 
			bool val = c[0] == 'i' ? 1 : 0; //用数组存了单词之后其实只用看第一个单词就可以分别出是哪个操作了
			int pa = find(a), pb = find(b); 
			if (pa == pb) {
				if ((dis[a] ^ dis[b]) != val) flag = 0;
			}
			else {
				p[pb] = pa;
				dis[pb] = val ^ dis[a] ^ dis[b]; //加加减减都可以变成异或的形式
				cou[pa][1] += cou[pb][dis[pb] ^ 1]; //这个地方想一下,根为0,如果pa、pb之间距离是偶数,1的数量就应该是加上pb中1的数量;如果是奇数,1的数量应该是原先pb中0的数量
				cou[pa][0] += cou[pb][dis[pb]];
			}
		}

		if (!flag) { puts("-1"); continue; }

		int res = 0;
		rep(i, n) if (find(i) == i) res += max(cou[i][0], cou[i][1]); //找到每个根节点,把它们的0和1中数量的最大值相加就是答案

		printf("%d\n", res);
	}

	return 0;
}

补充一下,自己又写了一遍,它老说那个递归runtime我也不知道为啥

写的时候发现,这个地方从一开始两个节点合并的时候就是,同类就距离为0,异类距离为1;

所以可以用^1之类的而且cnt数组也没有越界。^1不是只做最低位的异或运算,如果两个数高位没有了(都是0)就不做异或运算,所以说是按二进制最长的那个数做异或运算。

最后那个地方找根节点的cnt的时候,也可以用i == p[i]来判断,因为只有根节点的cnt在这是有意义的,其他的路径什么的在算res的时候是不用管的。

路径压缩可以写成两种形式:

int find(int x) { //带权并查集
	if (x != p[x]) {
		int u = p[x];
                p[x] = find(p[x]);
                dis[x] ^= dis[u];
	}
	return p[x];
}

以及

int find(int x) { 
	if (x != p[x]) {
		int root = find(p[x]);
		dis[x] ^= dis[p[x]];
		p[x] = root;
	}
	return p[x];
}

我又来补充了,好蠢

我自己做的时候,每当flag变为0我就直接break了,

这里如果flag == 0了也不能直接break掉啊。。。因为这里所有数据还没有输入完,直接break会导致后面的数据输入有问题啊。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值