整理的算法模板合集: ACM模板
luogu P4782 【模板】2-SAT 问题
我们在2-sat问题中会得到若干个关系式形如:
p∨q,也就是p||q,p或q,p为true或者q为true
而我们离散数学中学逻辑关系式中有这么一个转化关系:
p∨q == ¬p → q ∧ ¬q → p
也就是:
p || q == -p ->q && -q -> p
根据p和q 的真假性我们可以得到下列表格
原关系式 | 建图方式 |
---|---|
p ∨ q p \vee q p∨q | ¬ p → q ∧ ¬ q → p \neg p\rightarrow q\wedge\neg q\rightarrow p ¬p→q∧¬q→p |
¬ p ∨ q \neg p\vee q ¬p∨q | p → q ∧ ¬ q → ¬ p p\rightarrow q\wedge\neg q\rightarrow\neg p p→q∧¬q→¬p |
p ∨ ¬ q p\vee \neg q p∨¬q | ¬ p → ¬ q ∧ q → p \neg p\rightarrow \neg q\wedge q\rightarrow p ¬p→¬q∧q→p |
¬ p ∨ ¬ q \neg p\vee\neg q\space \space ¬p∨¬q | p → ¬ q ∧ q → ¬ p p\rightarrow\neg q\wedge q\rightarrow\neg p p→¬q∧q→¬p |
我们按照箭头建有向边,构成一个有向图。
每一条有向边,u->v
表示如果选择u,那么v也必须选择,不然就违反了关系式。
因此我们对这张图求强连通分量
那么,对于这张图中的每个强连通分量中的点一定要么同时选,要么同时不选。
判断无解:如果 x i , 0 x_{i,0} xi,0和 x i , 1 x_{i,1} xi,1在同一个强连通分量中,那么明显无解,因为二者对立不可能同时存在
这张拓扑图中,如果u可以到达v,那么u选择则v也必须选择。
tarjan算法dfs式地走一遍就是有向图的拓扑序,因为是用的栈存的节点,所以是拓扑逆序。
选择方案:在 x i , 0 x_{i,0} xi,0和 x i , 1 x_{i,1} xi,1中选择拓扑序较大的点。这样就可以避免产生冲突了。并且这种方法一定可以构造出解。
当 x x x 所在的强连通分量的拓扑序在 ¬ x \neg x ¬x所在的强连通分量的拓扑序之后取 x x x 为真 就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 color[x] < color[-x]
其中我们一般在2-sat问题中用i
表示false
,用i+n
表示true
时间复杂度为: O ( n + m ) O(n+m) O(n+m)
有 n 个布尔变量 x 1 ∼ x n x_1\sim x_n x1∼xn,另有 m 个需要满足的条件,每个条件的形式都是 「x_i为 true / false 或 x_j为 true / false」。比如 「x_1为真或 x_3为假」、「x_7为假或 x_2为假」。2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
//时间复杂度O(n+m)
//当 x 所在的强连通分量的拓扑序在 ¬x 所在的强连通分量的拓扑序之后取 x
//为真 ,注意我们得到的是拓扑逆序,所以要写成color[x] < color[¬x]
// 其中 i 表示 ¬x,用 i+n 表示 x
p ∨ q == ¬p → q ∧ ¬q → p
p || q == -p ->q && -q -> p
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstdlib>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 2000007, M = 5000007, INF = 0x3f3f3f3f;
int n, m;
int dfn[N], low[N], num;
bool vis[N], ins[N];
int a[N], scc_cnt;
int scc_id[N], color[N];
int stk[N], top;
int ver[M], nex[M], head[N], tot;
void add(int x, int y){
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
}
void tarjan(int x)
{
dfn[x] = low[x] = ++ num;
stk[++ top] = x;
ins[x] = true;
for(int i = head[x]; ~i; i = nex[i]){
int y = ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(ins[y])
low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]){
int y;
++ scc_cnt;
do{
y = stk[top -- ];
ins[y] = false;
scc_id[y] = scc_cnt;
color[y] = scc_cnt;
}while(x != y);
}
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++ i){
int p, q ,c ,d;
scanf("%d%d%d%d", &p, &c, &q, &d);
if(c && d){
// p 且 q -p-> q && -q -> p
add(p, q + n);
add(q, p + n);
}
else if(!c && d){
//p -> q && -q -> -p
add(p + n, q + n);
add(q, p);
}
else if(c && !d){
//-p -> -q && q -> p
add(p, q);
add(q + n, p + n);
}
else if(!c && !d){
//p -> -q && q -> -p
add(p + n, q);
add(q + n, p);
}
}
for(int i = 1; i <= 2 * n; ++ i){
if(!dfn[i])
tarjan(i);
}
for(int i = 1 ;i <= n; ++ i) {
if(color[i] == color[i + n]){
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(int i = 1; i <= n; ++ i)
printf("%d ", (color[i] > color[i + n]));
puts("");
return 0;
}