2-SAT问题详解

需要点亮的技能:

tarjan

2-SAT问题简述

看这样一个例子:
有一天,万恶的 c z h czh czh出了一道孙题,是XX树套XX树套XX树。
(以下为了方便,简称A树、B树、C树)
(由于 z y d zyd zyd很菜,所以这些树只能是线段树或平衡树)
有以下要求:
蒟蒻 z y d zyd zyd :A树是线段树 或 B树是平衡树。
巨神 m h y mhy mhy:A树是平衡树 或 C树是线段树。
巨神 y p y ypy ypy :A树是平衡树 或 C树是平衡树。
c z h czh czh冥思苦想良久,终于想出了一种解决方法:A树是平衡树,B树是平衡树,C树是线段树。
但由于 c z h czh czh过于毒瘤,他想要出一道树套树套树套……套树( n n n层嵌套), m m m个人提出了要求。 ( n , m &lt; = 1 0 6 ) (n,m&lt;=10^6) (n,m<=106)
现在 c z h czh czh要满足所有人的要求,但czh太余蠢,请你帮忙想出一种解决方案。

2 − s a t 2-sat 2sat问题标准题意为:传送门

怎么做呢?


如果每个人的要求超过2个,那就是一个NP完全问题,只能爆搜。
然而发现每个人的要求只有2个,显然可以转化成连边(zyd:怎么显然了qwq)。
回到例子:
把第 i i i棵树是线段树转化为 x i = 1 x_i=1 xi=1,是平衡树转化为 x i = 0 x_i=0 xi=0
蒟蒻 z y d zyd zyd的要求是 x 1 = 1 x_1=1 x1=1 x 2 = 0 x_2=0 x2=0
也就是说:
x 1 ! = 1 x_1!=1 x1!=1,那么一定满足 x 2 = 0 x_2=0 x2=0
x 1 = 0 x_1=0 x1=0若为真,则 x 2 = 0 x_2=0 x2=0为真。 ————(1)
x 2 ! = 0 x_2!=0 x2!=0,那么一定满足 x 1 = 1 x_1=1 x1=1
x 2 = 1 x_2=1 x2=1若为真,则 x 1 = 1 x_1=1 x1=1为真。 ————(2)

考虑如何把“若A为真,则B为真”的关系转化为边:
对于每个 i i i 1 ≤ i ≤ n 1 \leq i \leq n 1in),将 x i = 0 x_i=0 xi=0看作一个点,编号为 i i i x i = 1 x_i=1 xi=1看作一个点,编号为 i + n i+n i+n
然后对于(1),从 1 1 1 2 2 2连边;对于(2),从 n + 2 n+2 n+2 n + 1 n+1 n+1连边。
每个要求都连边一次,就把所有要求转化成一个图。

然后会发现,如果点 a a a和点 b b b在同一个强连通分量里面,那么 a a a b b b的真假是相同的。
证明: a a a b b b在同一个强连通分量中,说明 a a a可以有一条路径通向 b b b
a a a为真,则 a a a b b b路径上的所有点都为真, b b b也为真。
a a a为假,同理 b b b也为假。
所以同一个强连通分量里所有点的真假性都相同。

因此,如果 i i i i + n i+n i+n在同一个强连通分量里,说明 x i = 0 x_i=0 xi=0 x i = 1 x_i=1 xi=1的真假性相同,显然是不成立的,因此这时不存在解。反之则存在解。

如果存在解,考虑怎样找到一组解:
发现拓扑序靠后的一定是有拓扑序靠前的推出来的。
于是让拓扑序靠后的点为真即可。

毒瘤代码

题目链接:传送门

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
namespace I_Love {

const int Size=2000005;
int n,m,cnt,head[Size];
struct Edge {
	int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
int tim,top,tot,stk[Size],dfn[Size],low[Size],belong[Size];
bool vis[Size],val[Size];
void tarjan(int x) {
	dfn[x]=low[x]=++tim;
	stk[++top]=x;
	vis[x]=true;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(!dfn[nxt]) {
			tarjan(nxt);
			low[x]=min(low[x],low[nxt]);
		} else if(vis[nxt]) {
			low[x]=min(low[x],dfn[nxt]);
		}
	}
	if(dfn[x]==low[x]) {
		int y;
		tot++;
		while(y=stk[top--]) {
			belong[y]=tot;
			vis[y]=false;
			if(x==y)	return;
		}
	}
}
void Fujibayashi_Ryou() {
	freopen("testdata.in","r",stdin);
	n=read();
	m=read();
	for(re i=1; i<=m; i++) {
		int u=read();
		bool a=read();
		int v=read();
		bool b=read();
		//若(u==a)==false 则编号为u+n 
		AddEdge(u+(!a)*n,v+b*n);	//若u!=a 则 v=b 
		//若(v==a)==false 则编号为v+n 
		AddEdge(v+(!b)*n,u+a*n);	//若v!=b 则 u=a 
	}
	for(re i=1; i<=(n<<1); i++) {
		if(!dfn[i]) {
			tarjan(i);
		}
	}
	for(re i=1; i<=n; i++) {
		if(belong[i]==belong[i+n]) {
			puts("IMPOSSIBLE");
			return;
		}
	}
	puts("POSSIBLE");
	for(re i=1; i<=n; i++) {
		printf("%d ",belong[i]>belong[i+n]);
	}
}

}
int main() {
	I_Love::Fujibayashi_Ryou();
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值