浅谈2-SAT,理解万岁!
2-SAT
定义什么的我也说不明白。
是形如
\[(x_{i}\vee x_{j})\wedge (x_{k}\vee x_{l})\wedge ......\wedge (x_{n}\vee x_{m})
\]
这样的一个问题,其中 \(x\) 是布尔变量,可以看作一个条件,符合为真\((1)\),反之为假\((0)\),\(\vee\) 可以理解为逻辑或,\(\wedge\) 可以理解为逻辑与。
我们要做的,就是找到一组可行解使上面的式子为真,也就是满足所有条件。
我觉得这是一种图论的思想吧(
就是考虑这样的东西:
\[x_{1} = (a == 1) ? 1 : 0
\]
\[x_{2} = (b == 0) ? 1 : 0
\]
要令 \((\neg x_{1} \vee x_{2})\) 为真
这里的 $\neg $ 就是“非”。
那么无非就三种情况:
- \(x_{1} = 0 , x_{2} = 1\) 此时 \(\neg x_{1} = 1,x_{2} = 1\)
- \(x_{1} = 1 , x_{2} = 1\) 此时 \(\neg x_{1} = 0,x_{2} = 1\)
- \(x_{1} = 0 , x_{2} = 0\) 此时 \(\neg x_{1} = 1,x_{2} = 0\)
我们发现第一种可以先忽略,因为他算不上什么约束条件。
看后面两种,发现如果 \(\neg x_{1}\) 或者 \(x_{2}\) 为假,都要求另一个为真,据此我们可以建图:
- \(x_{1} -> x_{2}\) 若 \(x_{1}\) 为真,那么 \(x_{2}\) 必须为真
- \(\neg x_{2} -> x_{1}\) 若 \(\neg x_{2}\) 为真,那么 \(x_{1}\) 必须为真
分别对应两者为假的情况。有点绕,但是理解一下就好了!
然后你发现一个强连通分量里的点他们的值应该是相等的。
所以直接缩点就好了,无解的情况就是 \(x\) 和 \(\neg x\) 在同一个强连通分量里。
然后如何构造出可行解呢?
回想我们的连边方式,如果一个点向另一个点连边,那么被连的那个点一定是符合条件的那个!也就是我们需要的解。
这启示我们去弄一个拓扑序,对于 \(x\) 和 \(\neg x\), 拓扑序大的就是我们要的!
其实在强连通分量 tarjan 的时候我们求出来的是一个反着的拓扑序,想象一颗 \(dfs\) 树,越靠近叶子结点的强连通分量在栈中会越先弹出来,也就是他的强连通编号越小。反正一句话,强连通编号越小,拓扑序越大。所以做法就显然了:对于一个 \(x\) ,比较他们的强连通编号,小的就是要的答案!
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 2e6 + 5;
vector<int>g[N];
int n,m;
int dfn[N],low[N],sta[N],top,scccnt,dfncnt,sccnum[N];
void dfs(int u){
low[u] = dfn[u] = ++dfncnt;
sta[++top] = u;
for(int v : g[u]){
if(!dfn[v]){
dfs(v);
low[u] = min(low[u],low[v]);
}
else if(!sccnum[v])low[u] = min(low[u],dfn[v]);
}
if(low[u] == dfn[u]){
++scccnt;
while(1){
int x = sta[top--];
sccnum[x] = scccnt;
if(x == u)break;
}
}
}
signed main(){
read(n,m);
rep(i,1,m){
int x,a,y,b;
read(x,a,y,b);
g[x + n * (!a)].push_back(y + n * b);//x为假向y为真连边
g[y + n * (!b)].push_back(x + n * a);//y为假向x为真连边
}
rep(i,1,n*2)if(!dfn[i])dfs(i);
rep(i,1,n)if(sccnum[i] == sccnum[i+n])return puts("IMPOSSIBLE"),0;//无解
puts("POSSIBLE");
rep(i,1,n){
printf("%d ",sccnum[i] > sccnum[i+n]);//取小的,我这里 (i+n) 代表x,i代表为非x
}
}