例题链接:cf1971H
2-SAT本质上是将一些01变量间的限制关系以图的形式表现出来。
对于一个变量
a
i
a_i
ai,我们会建两个点分别表示
a
i
=
1
a_i = 1
ai=1和
a
i
=
0
a_i = 0
ai=0。
然后我们定义一条限制
l
i
m
(
a
i
,
a
j
,
x
,
y
)
lim(a_i,a_j,x,y)
lim(ai,aj,x,y) 为
a
i
=
x
a_i =x
ai=x和
a
j
=
y
a_j = y
aj=y不能同时出现。则对于一条限制,我们会连两条边:
a
i
=
x
a_i=x
ai=x to
a
j
=
y
xor
1
a_j=y \text{ xor } 1
aj=y xor 1 和
a
j
=
y
a_j = y
aj=y to
a
i
=
x
xor
1
a_i = x \text{ xor } 1
ai=x xor 1。 表示如果我们选了
a
i
=
x
a_i = x
ai=x,就只能再选
a
j
=
y
xor
1
a_j = y \text{ xor } 1
aj=y xor 1。另一条边类似。
然后判定可行解只需要对全图跑SCC,如果 a i = 0 a_i = 0 ai=0和 a i = 1 a_i = 1 ai=1在同一个SCC里则不可行。感性理解一下就是我们选了 a i = 0 a_i = 0 ai=0并且在一长串路径后得到我们还得选 a i = 1 a_i = 1 ai=1,就矛盾了。
例题解法:每一列的三个式子中里至少要有两个为1。可以看成任意两个式子里要有一个为1,所以就是任意两个式子不能为0。一列等价于3条限制。建完图之后直接跑2-SAT就行。
#include<bits/stdc++.h>
using namespace std;
struct strongly_connected{
vector<vector<int>> gph;
void init(int n){
gph.clear();
gph.resize(n);
}
void add_edge(int s,int e){
gph[s].push_back(e);
}
vector<int> val,comp,z,cont;
int Time,ncomps;
template<class G,class F> int dfs(int j, G&g, F f){
int low=val[j]=++Time,x;z.push_back(j);
for(auto e:g[j])if(comp[e]<0)
low=min(low, val[e]?:dfs(e,g,f));
if(low==val[j]){
do{
x=z.back();
z.pop_back();
comp[x]=ncomps;
cont.push_back(x);
}while(x!=j);
f(cont);cont.clear();
ncomps++;
}
return val[j]=low;
}
template<class G,class F>void scc(G& g, F f){
int n=g.size();
val.assign(n,0);comp.assign(n,-1);
Time=ncomps=0;
for(int i=0;i<n;i++)if(comp[i]<0)dfs(i,g,f);
}
int piv;
void get_scc(int n){
scc(gph,[&](vector<int> &v){});
for(int i=0;i<n;i++){
comp[i]=ncomps-comp[i];
}
piv=ncomps;
}
}scc;
struct twosat{
strongly_connected scc;
int n;
void init(int _n){scc.init(2*_n);n=_n;}
int NOT(int x){return x>=n?(x-n):(x+n);}
void add_edge(int x,int y){
if(x<0)x = (-x)+n;
if(y<0)y = (-y)+n;
scc.add_edge(x,y);scc.add_edge(NOT(y),NOT(x));
}
bool satisfy(int *res){//res存的是一组合法解
scc.get_scc(2*n);
for(int i=0;i<n;i++){
if(scc.comp[i]==scc.comp[NOT(i)]) {
return 0;
}
if(scc.comp[i]<scc.comp[NOT(i)])res[i]=0;
else res[i]=1;
}
return 1;
}
}twosat;
const int maxn=505;
int t,n,a[3][maxn],res[maxn];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>t;
while(t--){
cin>>n;
twosat.init(n+1);//因为题目输入从1开始,但是我2-SAT的模板下标从0开始。
for(int i=0;i<3;i++){
for(int j=1;j<=n;j++)cin>>a[i][j];
}
for(int j=1;j<=n;j++){
for(int i=0;i<3;i++){
int nxt=(i+1)%3;
twosat.add_edge(-a[i][j],a[nxt][j]);
}
}
if(twosat.satisfy(res))puts("YES");
else puts("NO");
}
return 0;
}