题目情境#
题目链接:https://www.luogu.com.cn/problem/P4782 ↗
题目描述
有 个布尔变量 ,另有 个需要满足的条件,每个条件的形式都是 「 为 true / false 或 为 true / false」。比如 「 为真或 为假」、「 为假或 为假」。
2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入格式
第一行两个整数 和 ,意义如题面所述。
接下来 行每行 个整数 , , , ,表示 「 为 或 为 」()
输出格式
如无解,输出 IMPOSSIBLE;
否则输出 POSSIBLE,下一行 个整数 (),表示构造出的解。
输入 #1
3 1
1 1 3 0plaintext输出 #1
POSSIBLE
0 0 0plaintext说明/提示
, 前 个点卡小错误,后面 个点卡效率。
由于数据随机生成,可能会含有 10 0 10 0 之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里。
算法解析:#
2-SAT 把每个变量 拆成两个点: 表示 为真、 表示 为假。条件「 为 或 为 」等价于两条蕴含边:
- (若 不满足 ,则 必须满足 )
- (对称)
建图后用 Tarjan 求强连通分量。若某个变量 的两种取值( 与 )在同一个 SCC 中,则矛盾无解,输出 IMPOSSIBLE;否则有解:对每个变量取 编号较大的那个取值(Tarjan 求出的 编号是拓扑序的逆序,取大者即取拓扑序靠后的可行值)。复杂度 。
Python代码实现#
import sys
sys.setrecursionlimit(10**6)
input = sys.stdin.readline
def tarjan(sz, edges):
g = [[] for _ in range(sz)]
for u, v in edges:
g[u].append(v)
dfn = [0] * sz; low = [0] * sz; scc = [0] * sz
stk = []; in_stk = [False] * sz
time = 0; cnt = 0
def dfs(u):
nonlocal time, cnt
time += 1
dfn[u] = low[u] = time
stk.append(u); in_stk[u] = True
for v in g[u]:
if not dfn[v]:
dfs(v)
low[u] = min(low[u], low[v])
elif in_stk[v]:
low[u] = min(low[u], dfn[v])
if low[u] == dfn[u]:
cnt += 1
while True:
x = stk.pop(); in_stk[x] = False
scc[x] = cnt
if x == u: break
for i in range(sz):
if not dfn[i]:
dfs(i)
return scc
n, m = map(int, input().split())
edges = []
for _ in range(m):
i, a, j, b = map(int, input().split())
xi = i if a == 1 else i + n # xi 为 a 的结点
xj = j if b == 1 else j + n # xj 为 b 的结点
xi_neg = i + n if a == 1 else i # xi 不满足 a
xj_neg = j + n if b == 1 else j # xj 不满足 b
edges.append((xi_neg, xj)) # ¬xi_a -> xj_b
edges.append((xj_neg, xi)) # ¬xj_b -> xi_a
scc = tarjan(2 * n + 1, edges)
ans = []
for i in range(1, n + 1):
if scc[i] == scc[i + n]:
print("IMPOSSIBLE")
sys.exit()
ans.append('1' if scc[i] > scc[i + n] else '0')
print("POSSIBLE")
print(' '.join(ans))pythonC++代码实现#
// 2-SAT+tarjan O(n+m)
#include<bits/stdc++.h>
using namespace std;
const int N=2000005;
int n,m;
int hd[N],to[N],ne[N],idx;
int dfn[N],low[N],tim,stk[N],top,scc[N],cnt;
void add(int a,int b){
to[++idx]=b,ne[idx]=hd[a],hd[a]=idx;
}
void tarjan(int x){
dfn[x]=low[x]=++tim;
stk[++top]=x;
for(int i=hd[x];i;i=ne[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(!scc[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
++cnt;
for(int y=-1;y!=x;) scc[y=stk[top--]]=cnt;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i,a,j,b;m--;){
scanf("%d%d%d%d",&i,&a,&j,&b);
add(i+!a*n,j+b*n); //xi拆成i和i+n
add(j+!b*n,i+a*n);
}
for(int i=1;i<=2*n;i++)if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)if(scc[i]==scc[i+n]){
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=1;i<=n;i++)printf("%d ",scc[i]>scc[i+n]);
return 0;
}c