2-sat 学习笔记
\(\text{2-sat}\) 学习笔记
有这样一类问题,有多个变量 \(a_{1\to n}\),每个变量的取值范围为 \(\{0,1\}\),给出 \(m\) 条限制条件,形如 \((\lor_{i=1}^{k}a_{p_i}=x_i)=\text{true}\) 的形式,需要你求解是否有可行解。这就是 \(\text{k-sat}\) 问题,已被证明是 \(\text{NPC}\) 问题,但 \(\text{2-sat}\) 问题是可以在多项式复杂度内求解的问题。
思想
\(\text{2-sat}\) 问题一般形如 \(n\) 个变量每个变量有 \(2\) 中不同的取值且只能取一个,并给出 \(k=2\) 时的形式限制条件,需要求解可行的变量取值。
考虑将每一个点拆成两个点,分别表示是否选择该取值,那么显然两个中只能有一个满足。以这一条为基础,我们想要证明整个方程无解必须说明当一个取值为 \(\text{true}\) 时,另一个也一定为 \(\text{true}\)。考虑用一条有向边传递这个信息,即 \(a\to b\) 表示若 \(a\) 为 \(\text{true}\) 则 \(b\) 一定为 \(\text{true}\)。那么当且仅当拆成的两个点处在同一个强连通分量时方程无解。
考虑如何构造,将强连通分量所点后得到一个 \(\text{DAG}\),考虑结合拓扑序构造,若两个点中拓扑序在前的那个点为 \(\text{true}\),则另一个显然为 \(\text{true}\),所以取 \(\text{true}\) 的点只能是拓扑序靠后的点。但实际实现中我们没有必要真正去缩点,因为 Tarjan
实现时强连通分量的编号相当于逆拓扑序,也就是反着的拓扑序,我们取所在强连通分量编号较小的那个点就行。时间复杂度为 \(O(n+m)\),\(n\) 为点数,\(m\) 为边数。
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define id(x,t) ((t)?x:x+n)
const int N=1e6+5;
int n,m,cnt,num,top,scc,tot;
int head[N<<1],dfn[N<<1],low[N<<1],col[N<<1],stk[N<<1];
struct Edge{
int v,to;
}e[N<<1];
il void AddEdge(int x,int y){
e[cnt]={y,head[x]};
head[x]=cnt++;
return ;
}
il void Tarjan(int x){
dfn[x]=low[x]=++num;
stk[++top]=x;
for(int i=head[x];i;i=e[i].to){
int y=e[i].v;
if(!dfn[y]){
Tarjan(y);
low[x]=min(low[x],low[y]);
}else if(!col[y])low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
++scc;
int y;
do{
y=stk[top--];
col[y]=scc;
}while(y!=x);
}
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,a,y,b;cin>>x>>a>>y>>b;
AddEdge(id(x,a^1),id(y,b));
AddEdge(id(y,b^1),id(x,a));
}
for(int i=1;i<=n*2;i++){
if(!dfn[i])Tarjan(i);
}
for(int i=1;i<=n;i++){
if(col[i]==col[i+n]){
cout<<"IMPOSSIBLE";
return 0;
}
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;i++)cout<<(col[i]<col[i+n])<<" ";
return 0;
}
常见模型
赋值型
形如 \(a=x\) 的条件,我们可以考虑用另一个点指向这个点,这样另一个点绝对不能为 \(\text{true}\),也就成功限制了这个条件。
推导型
形如 \(a\to b\) 的条件,我们可以直接连边,但是这样做不够,因为通过这个命题我们还能推导出一个命题 \(\lnot b\to\lnot a\),这个命题是前面那个命题的逆否命题,是保证正确性必须有的命题。
逻辑或型
形如 \(a\lor b\) 的条件,通过这个条件我们能够推出,\(\lnot a\to b,\lnot b\to a\),直接连边即可
逻辑与型
形如 \(a\land b\) 的条件,通过这个条件我们能够推出 \(a=\text{true},b=\text{true}\)。
逻辑异或型
形如 \(a\oplus b\) 的条件,通过这个条件我们能够推出 \(a\to \lnot b,\lnot a\to b,b\to\lnot a,\lnot b\to a\),事实上,我们发现这构成了两个环,因此在某些题目中我们可以利用这一点,将两个环提前缩为一点达成优化时间、空间复杂度的目的。
常见优化
无用点忽略
指一些点只起到传递作用,而没有其他限制作用,这样的点在一定程度上是无用的,为了节省复杂度可以直接删去。
传递点加入
在一些边数过多的图中,我们可以用一些传递点(也就是单独的一个点,没有配对)来传递信息,这样我们可以用一条边代替多条边的作用,起到优化的效果。比如对于 \(n\) 个点每个点向 \(m\) 个点连边,这是 \(O(nm)\) 的,但我们可以新增一个点 \(P\),让 \(n\) 个点先向 \(P\) 连边,接着 \(P\) 向 \(m\) 个点连边,复杂度便降至 \(O(n+m)\)。这个思路在所有的优化建图中均可以使用。