2-SAT
SAT
给定 \(n\) 个布尔变量 \(x_1,x_2,\cdots,x_n\),和 \(m\) 组关系,每组关系给定 \(i_1,i_2,\cdots,i_k,a_1,a_2,\cdots,a_k\),表明:
\[[x_{i_1}=a_1]\lor[x_{i_2}=a_2]\lor[x_{i_3}=a_3]\lor\cdots\lor[x_{i_k}]=1 \]求任意一组解。
SAT 是 Satisfiability(适应性问题)的缩写。
\(k\) -SAT 是 NP 完全的,只能打暴力。但是 2-SAT 还是有比较好的做法的,可以 \(\mathcal O(n)\) 给出一组可行解。
2-SAT
有 \(n\) 个布尔变量 \(x_1\sim x_n\),另有 \(m\) 个需要满足的条件,每个条件的形式都是 \([x_i=a]\lor[x_j=b]=1\)。
- 若无解,输出
IMPOSSIBLE。- 否则输出
POSSIBLE,第二行输出 \(n\) 个数 \(x_1,x_2,\cdots,x_n\) 表示一组可行解。
建图
「\(x_i=a\) 或 \(x_j=b\)」明显不好做,因此可以考虑将其转化为一个「确定性」条件。
例如「\(x_i=1\) 或 \(x_j=0\)」,那么当 \(x_i=0\) 的时候,必然有 \(x_j=0\)。也就是说,当「\(x_i=a\) 或 \(x_j=b\)」其中一个条件不成立时,另一个一定成立。
用一张有向图来存储这些信息,记 \(i\) 表示 \(x_i=1\) 的状态,\(\lnot i=i+n\) 表示 \(x_i=0\) 的状态。
-
\(a=0,b=0\):
- \(x_i=1\Rightarrow x_j=0\),建边 \(i\rightarrow\lnot j\)。
- \(x_j=1\Rightarrow x_i=0\),建边 \(j\rightarrow\lnot i\)。
-
\(a=0,b=1\):
- \(x_i=1\Rightarrow x_j=1\),建边 \(i\rightarrow j\)。
- \(x_j=0\Rightarrow x_i=0\),建边 \(\lnot j\rightarrow\lnot i\)。
-
\(a=1,b=0\):
- \(x_i=0\Rightarrow x_j=0\),建边 \(\lnot i\rightarrow\lnot j\)。
- \(x_j=1\Rightarrow x_i=1\),建边 \(j\rightarrow i\)。
-
\(a=1,b=1\):
- \(x_i=0\Rightarrow x_j=1\),建边 \(\lnot i\rightarrow j\)。
- \(x_j=0\Rightarrow x_i=1\),建边 \(\lnot j\rightarrow i\)。
强连通分量
假设点 \(i\) 代表的成立,那么 \(i\) 能够通过若干条有向边到达的所有点 \(j\) 代表的状态也成立。此时,若 \(i\) 能够到达 \(\lnot i\),则自相矛盾,无解。
为了快速判断 \(i\) 能否到达 \(\lnot i\),可以通过 Tarjan 求强连通分量(SCC)。
解的构造
现在我们已经确定了有解。
显然,\(i,\lnot i\) 只能有一个点被选择,考虑选则拓扑序尽可能大的点,因为这样会让这个节点「更晚被遍历」,产生冲突的概率更小。
又因为已经确定有解,因此这样一定能构造一组可行解。(可能存在其他解。)
实际实现上,可以不需要再进行一遍拓扑排序,因为 Tarjan 求出的 SCC 编号就是反拓扑序。同时,整个图有可能不连通。
AC 代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1e6;
int n;
vector<int>g[N<<2|1];
int dfn[N<<1|1],id[N<<1|1];
bool ans[N+1];
void Tarjan(int x){
static int cnt,low[N<<1|1];
static bool in[N<<1|1];
static vector<int>s;
dfn[x]=low[x]=++cnt;
in[x]=true;
s.push_back(x);
for(int i:g[x]){
if(!dfn[i]){
Tarjan(i);
low[x]=min(low[x],low[i]);
}else if(in[i]){
low[x]=min(low[x],dfn[i]);
}
}
if(dfn[x]==low[x]){
static int size=0;
size++;
while(s.back()!=x){
in[s.back()]=false;
id[s.back()]=size;
s.pop_back();
}
in[s.back()]=false;
id[s.back()]=size;
s.pop_back();
}
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int m;
cin>>n>>m;
while(m--){
int i,a,j,b;
cin>>i>>a>>j>>b;
if(!a&&!b){
g[i].push_back(j+n);
g[j].push_back(i+n);
}else if(!a&&b){
g[i].push_back(j);
g[j+n].push_back(i+n);
}else if(a&&!b){
g[j].push_back(i);
g[i+n].push_back(j+n);
}else{
g[i+n].push_back(j);
g[j+n].push_back(i);
}
}
for(int i=1;i<=(n<<1);i++){
if(!dfn[i]){
Tarjan(i);
}
}
for(int i=1;i<=n;i++){
if(id[i]==id[i+n]){
cout<<"IMPOSSIBLE\n";
return 0;
}
}
for(int i=1;i<=n;i++){
if(id[i]<id[i+n]){
ans[i]=true;
}
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}

浙公网安备 33010602011771号