[学习笔记]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)<<" ";
}


posted @ 2022-01-23 08:49  fhq_treap  阅读(42)  评论(0编辑  收藏  举报