【扩展域并查集】Codeforces Round #747 (Div. 2) D. The Number of Imposters

160 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目链接:
codeforces.com/problemset/…

有 n 个人,每个人可能是诚实的人或者说谎的人。
诚实的人永远说真话,说谎的人永远说假话。
有 m 个条件,分别表示 i 说 j 是诚实的人/说谎的人。
你需要判断在这些限制条件下,最多有可能有几个诚实的人,或输出 -1 声明不可能存在这种情况。


分析规则:

  • 若A说B诚实,则AB要么都诚实,要么都是骗子
  • 若A说B骗子,则A诚实B骗子 ,或者A骗子B诚实

可以发现当一个人诚实和骗子确定下来,与他相关的人都会确定下来。

考虑用一个并查集进行维护,并查集开两倍的空间。

x表示表示一个人,则x表示他诚实,x + n表示他是骗子。所以并查集前半部分的空间开的是诚实的人的,后半部分开的是骗子的人的。

注意: 初始化时,只有前半部分的人的sz数组初始化为1,其余都为0

如果find(x)==find(x+n)find(x)==find(x+n),则会产生冲突,结果为-1


并查集中因为一正一反,如果有两种情况,那么这两种情况是相反的。用个图来理解

image.png

#include<bits/stdc++.h>
using namespace std;
using ll = long long; 
using pii = pair<int, int>;
const int N = 1e5 + 5;

struct dsu
{
	vector<int> f, sz;
	dsu(int n)
	{
		f.resize(n);
		sz.resize(n, 1);
 		for(int i = 1; i < n; i++) f[i] = i;
	}
	int find(int x)
	{
		return x == f[x] ? x : f[x] = find(f[x]);
	}
	void merge(int x, int y)
	{
		x = find(x);
		y = find(y);
		if(x == y) return;
		if(sz[x] < sz[y])  swap(x, y);
		f[y] = x;
		sz[x] += sz[y];
		sz[y] = 0;
	}
	
};
void solve()
{
	int n, m;
	cin >> n >> m;
	
	dsu tr(2 * n + 1);
	for(int i = n + 1; i <= 2 * n; i++)
		tr.sz[i] = 0;
	for(int i =1; i <= m; i++)
	{
		int x, y;
		string s;
		cin >> x >> y >> s;
		if(s[0] == 'c') tr.merge(x, y), tr.merge(x + n, y + n);
		else tr.merge(x, y + n), tr.merge(x + n, y);
	}
	
	int res = 0;
	for(int i = 1; i <= n; i++)
	{
		int x = tr.find(i), y = tr.find(i + n);
		if(x == y) 
		{
			cout << -1 << "\n";
			return;
		}
		res += max(tr.sz[x], tr.sz[y]);
		tr.sz[x] = 0, tr.sz[y] = 0;
	}
	cout << res << "\n";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t;
	cin >> t;
	while(t--) solve();
	return 0;
}