[学习笔记]2-SAT
2-SAT的定义
\(2-SAT\)是对于一类限制问题类似于\(a_1 or a_2 = 0\)之类的每个限制只有两个元素,求解一个合法的全体序列问题。
解法
发现此类条件具有指向性。
拆点。
连\(u\to v\),表示若\(u\)成立则\(v\)一定成立
若\(u\)可以推出\(v\)则\(u\)非法,\(v\)合法。
有向无环图中,合法点的拓扑序比非法点大
强连通分量中,任意点合法则全体点合法。
可以发现强连通分量的编号实际上和拓扑序相关
同一元素拆点后强连通分量编号小的点是合法点
若两点在一起则无解。
2-SAT
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 3000005
inline ll read(){
char C=getchar();
ll A=0 , F=1;
while(('0' > C || C > '9') && (C != '-')) C=getchar();
if(C == '-') F=-1 , C=getchar();
while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
return A*F;
}
template <typename T>
void write(T x)
{
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x/10);
putchar(x % 10 + '0');
return;
}
int n,m;
using std::vector;
vector<int>T[N];
inline int D(int x,int p){
return x + !p * n;
}
int cnt;
int dfn[N],low[N];
bool in[N];
std::stack<int>Q;
int scc[N];
int scccnt;
inline void tarjan(int u){
low[u] = dfn[u] = ++cnt;
Q.push(u),in[u] = 1;
for(int i = 0;i < T[u].size();++i){
int v = T[u][i];
if(!dfn[v])
tarjan(v),low[u] = std::min(low[v],low[u]);
else
if(in[v])
low[u] = std::min(low[u],dfn[v]);
}
if(low[u] == dfn[u]){
++scccnt;
int x;
while(x = Q.top()){
scc[x] = scccnt;
in[x] = 0;
Q.pop();
if(x == u)
break;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;++i){
int x,y,a,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
T[D(x,!a)].push_back(D(y,b));
T[D(y,!b)].push_back(D(x,a));
}
for(int i = 1;i <= 2 * n;++i)
if(!dfn[i])
tarjan(i);
for(int i = 1;i <= n;++i)
if(scc[D(i,1)] == scc[D(i,0)]){
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i = 1;i <= n;++i)
std::cout<<(scc[D(i,1)] < scc[D(i,0)] ? 1 : 0)<<" ";
}