[复习] 种类并查集 & 2-XOR-SAT
[复习] 种类并查集
种类并查集也可叫做扩展域并查集。
前言
自从两年多前刚学并查集时过了食物链后,就再也没有写过种类并查集。
今天回顾一下。
例题 1 食物链
题目大意:有 \(n\) 个动物,每个动物属于 \(A,B,C\) 种中的一种,\(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\)。每次给出 \(x,y\) 同类或 \(x\) 吃 \(y\) 的信息,看是否合法。
首先 \(x,y\) 同类可以想到用并查集维护。那么 \(x\) 吃 \(y\) 怎么维护呢。
当 \(x\) 吃 \(y\) 时,我们不能将 \(x,y\) 放到一个并查集里表示它们不同。否则如果 \(a\to b,b\to c,c\to d\),那此时 \(a,d\) 其实是同类的。
考虑一个动物,我们并不需实际区分它是哪个类别,只需考虑它和其他动物的关系即可。
于是考虑并查集取出 \(3n\) 个点,\(1\sim n\) 为第一类,\(n+1\sim 2n\) 为第二类,\(2n+1\sim 3n\) 为第三类。
我们这样表示它们的关系:
对于同一类中的点 \((x,y)\),如果它们在同一个集合中,则 \((x,y)\) 同类。
对于不在同一类中的点 \((x+kn,y+k'n)\),如果它们在同一个集合中,则 \((x,y)\) 不同类。
第一类吃第二类,第二类吃第三类,第三类吃第一类。
于是缩点就变成了:
- 如果 \(x,y\) 同类,那么 \((x,y),(x+n,y+n),(x+2n,y+2n)\) 缩点。
- 如果 \(x\to y\),那么 \((x,y+n),(x+n,y+2n),(x+2n,y)\) 缩点。
2-XOR-SAT
SAT 问题,是一个 NP 完全问题,求 \(n\) 个布尔变量,有若干条限制,每条限制形如给定若干布尔变量分别为 \(0/1\),要至少有一个布尔变量满足。
- 2-SAT 问题,每条限制由两个变量组成,可以用 SCC 缩点在 \(O(n+m)\) 解决。
- XOR-SAT 问题,将 SAT 问题的限制改为用异或连接,每条限制要使最终异或值为 \(0/1\),可以用高斯消元在 \(O(n^3)\) 解决。
- 2-XOR-SAT 问题,每条限制给定两个变量和它们的异或值,要求构造方案或判断无解。
2-XOR-SAT 问题可用本篇的种类并查集做。
还是 \(1\sim n\) 表示第一类,\(n+1\sim 2n\) 表示第二类,同类点缩在一起表示它们相等,不同类点缩在一起表示它们不等。
我们知道,异或值指示了两个布尔数是否相同。
于是显然有这样的缩点策略:
- 如果 \(a_x\oplus a_y=1\),那么缩点 \((x,y+n),(x+n,y)\) 表示 \((x,y)\) 不等。
- 如果 \(a_x\oplus a_y=0\),那么缩点 \((x,y),(x+n,y+n)\) 表示 \((x,y)\) 相等。
例题 2 arc183_c
题目大意:
有 \(n\) 个人,每个人是诚实或撒谎,清醒或糊涂。其中:
清醒的诚实人说真话。
清醒的撒谎人说假话。
糊涂的诚实人说假话。
糊涂的撒谎人说真话。
给定 \(m\) 个条件,形如 \(a,b\in [1,n],c=0/1\),表示 \(a\) 号人说 \(b\) 号人是诚实或撒谎。
我们可以设布尔变量 \(p_i\) 为 \(0/1\) 表示 \(i\) 是诚实或是撒谎,\(q_i\) 为 \(0/1\) 表示 \(i\) 是清醒或是糊涂。
那么条件 \((a,b,c)\) 就相当于要满足 \(p_a\oplus q_a\oplus p_b=c\)。
这是三元的,并不好,我们可以考虑设 \(r_i=p_i\oplus q_i\),那么条件就变成了 \(r_a\oplus p_b=c\),我们可以求 \(r_i,p_i\),然后用 \(q_i=r_i\oplus p_i\) 算出 \(q_i\)。
关于方案,如果最后合法了,那么对于缩在一起的点(包括不同类点),我们指定一个点随便取一个值后,就能全部确定这些点。
AC 代码:
const int N=2e5+5;
int n,m;
int fa[N*4];
int getfa(int x){
if(fa[x]==x)return x;
return fa[x]=getfa(fa[x]);
}
void merge(int x,int y){
if(getfa(x)!=getfa(y))
fa[getfa(x)]=getfa(y);
}
int nx(int x){
if(x<=2*n)return x+2*n;
return x-2*n;
}
int r(int x){
return x;
}
int p(int x){
return x+n;
}
void no(){
write("-1");
exit(0);
}
vector<int> t[N*4];
int ans[N*4],vis[N*4];
signed main(){
read(n,m);
fo(i,1,n*4)fa[i]=i;
fo(i,1,m){
int a,b,c;
read(a,b,c);
if(c){
if(getfa(r(a))==getfa(p(b))){
no();
}
merge(r(a),nx(p(b)));
merge(nx(r(a)),p(b));
}
else{
if(getfa(r(a))==getfa(nx(p(b)))){
no();
}
merge(r(a),p(b));
merge(nx(r(a)),nx(p(b)));
}
}
fo(i,1,n*2)if(getfa(i)==getfa(nx(i)))no();
fo(i,1,n*4)t[getfa(i)].push_back(i);
fo(i,1,n*4){
for(auto j:t[i]){
if(j<=2*n&&!vis[j]){
vis[j]=1;
ans[j]=0;
}
else if(j>2*n&&!vis[j-2*n]){
vis[j-2*n]=1;
ans[j-2*n]=1;
}
}
}
fo(i,1,n)write(ans[i]^ans[i+n]);
return 0;
}