2-SAT 模板题

题面

2-SAT模板题

题目描述

n n n 个布尔变量 x 1 x_1 x1 ~ x n x_n xn,满足 m m m 个形如 x i = t r u e / f a l s e x_i=true/false xi=true/false x j = t r u e / f a l s e x_j=true/false xj=true/false 的条件 。

输入

第一行两个整数 n n n m m m,意义如题面所述。接下来的 m m m 行,每行四个整数, x , a , y , b x,a,y,b x,a,y,b ( 1 ≤ x , y ≤ n    a , b ∈ { 0 , 1 } 1\leq x,y\leq n\ \ a,b \in \{0,1\} 1x,yn  a,b{0,1})。

输出

无解输出 I M P O S S I B L E IMPOSSIBLE IMPOSSIBLE
有解输出 P O S S I B L E POSSIBLE POSSIBLE。并输出构造出来的解。
采用 S p e c i a l   J u d g e Special \ Judge Special Judge

样例

输入

3 1
1 1 3 0

输出

POSSIBLE
0 0 0

题解

连边

  • 定义点 x x x 表示 ( x − ( x > n ) ∗ n ) = ( x > n ) (x-(x>n)*n)=(x>n) (x(x>n)n)=(x>n),也就是:
  • 如果点 x ≤ n x\leq n xn,则代表第 x x x 个数为 0 0 0
  • 如果点 x ≥ n + 1 x\geq n+1 xn+1,则代表第 x − n x-n xn 个数为 1 1 1
  • 定义边 ( x , y x,y x,y) 由 ( x − ( x > n ) ∗ n ) = ( x > n ) (x-(x>n)*n)=(x>n) (x(x>n)n)=(x>n) 可以推出 ( y − ( y > n ) ∗ n ) = ( y > n ) (y-(y>n)*n)=(y>n) (y(y>n)n)=(y>n)

对于条件 ( x = a ∣ ∣ y = b x=a||y=b x=ay=b),我们可以转化为:
x = a   x o r   1 → y = b x=a\ xor\ 1\rightarrow y=b x=a xor 1y=b y = b   x o r   1 → x = a y=b\ xor\ 1\rightarrow x=a y=b xor 1x=a
由此我们就知道了该怎么连边。

add(x + (a ^ 1) * n,y + b * n);
add(y + (b ^ 1) * n,x + a * n);

判无解

于是连完后,我们跑一遍 t a r j a n tarjan tarjan,求出其中的强连通分量,如果 x x x x + n x+n x+n 同处于一个强连通分量内,那么显然无解。原因很显然,这意味着我们从 x = 0 x=0 x=0 推出了 x = 1 x = 1 x=1

构造值

接下来是构造值的问题。
我用的是小蓝书的第二种方法,这里就不赘述原理了。

代码

#include<cstdio>
#include<iostream>
using std::min;
const int N = 2e6 + 5;
struct edge {
	int next,to;
}a[N << 1];
int head[N],opp[N],n,m,a_size = 1;
inline void add(int u,int v) {
	a[++a_size] = (edge){head[u],v};
	head[u] = a_size;
}
int dfn[N],low[N],sta[N],c[N],top = 0,cnt = 0,num = 0;
bool ins[N];
void tarjan(int x) {
	dfn[x] = low[x] = ++num;
	sta[++top] = x; ins[x] = true;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}
		else if(ins[y])
			low[x] = min(low[x],dfn[y]); 
	}
	if(dfn[x] == low[x]) {
		int y; cnt++;
		do {
			y = sta[top--]; ins[y] = false;
			c[y] = cnt;
		}while(x != y);
	}
}
inline int read() {
	int x = 0,flag = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')flag = -1;ch = getchar();}
	while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
	return x * flag;
}
int main() {
	n = read(),m = read();
	for(int i = 1; i <= m; i++) {
		int x = read(),a = read(),y = read(),b = read();
		add(x + (a ^ 1) * n,y + b * n);
		add(y + (b ^ 1) * n,x + a * n);
	} n <<= 1;
	for(int i = 1; i <= n; i++)
		if(!dfn[i]) tarjan(i);
	n >>= 1;
	for(int i = 1; i <= n; i++)
		if(c[i] == c[i + n]) {puts("IMPOSSIBLE"); return 0;}
	puts("POSSIBLE");
	for(int i = 1; i <= n; i++)
		printf("%d ",c[i] > c[n + i]);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值