2-SAT 学习笔记

2-SAT 学习笔记

有一类生活中的问题:

一个人邀请了 A,B,C,D 四个人来他的 party,但是 A 和 B 有矛盾,所以他俩不会一起来,C 和 D 也有矛盾不会一起来,要求找到一种合适的方式来邀请他们,判断是否有这种方案,并且找出这种方案。

把这个问题抽象一下,有 \(n\)\(\texttt{bool}\) 变量,有 \(m\) 组形似 \((a,flag_a,b,flag_b)\) 的条件,表示当 \(a=flag_a\) 时,\(b\neq flag_b\),或者当 \(b=flag_b\) 时,\(a\neq flag_a\),要求判断这些条件是否可行,如果可行,给出一种具体赋值方案。

如果把这个说法再简化一下,就是条件 \((A,B)\),满足 \(A\land (\lnot B)=1\)\((\lnot A)\land B=1\)

前置知识:强连通分量(本博客采用 Tarjan 算法)。

P4782 【模板】2-SAT 问题

题目描述

\(n\) 个布尔变量 \(x_1\)\(\sim\)\(x_n\),另有 \(m\) 个需要满足的条件,每个条件的形式都是 「\(x_i\)true / false\(x_j\)true / false」。比如 「\(x_1\) 为真或 \(x_3\) 为假」、「\(x_7\) 为假或 \(x_2\) 为假」。

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

输入格式

第一行两个整数 \(n\)\(m\),意义如题面所述。

接下来 \(m\) 行每行 \(4\) 个整数 \(i\), \(a\), \(j\), \(b\),表示 「\(x_i\)\(a\)\(x_j\)\(b\)」(\(a, b\in \{0,1\}\))

输出格式

如无解,输出 IMPOSSIBLE;否则输出 POSSIBLE

下一行 \(n\) 个整数 \(x_1\sim x_n\)\(x_i\in\{0,1\}\)),表示构造出的解。

样例 #1

样例输入 #1

3 1
1 1 3 0

样例输出 #1

POSSIBLE
0 0 0

提示

\(1\leq n, m\leq 10^6\) , 前 \(3\) 个点卡小错误,后面 \(5\) 个点卡效率。

由于数据随机生成,可能会含有(10 0 10 0)之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里。

Solution

此题就是最显然的 \(\texttt{2-SAT}\) 问题(毕竟模板)。

尝试建图,将每一个条件建成一个点,比如有两个点 \(A,B\),边 \(A\rightarrow B\) 就表示当 \(A\) 满足时,\(B\) 也会满足。根据这样的建图方式,读入数据 \((A,B)\),就可以连两条边 \(A\rightarrow(\lnot B)\)\(B\rightarrow(\lnot A)\)。不难发现,在同一个强连通分量中的变量的值一定是相同的。换言之,如果 \(A\)\(\lnot A\) 在同一个强连通分量中,就是无解的情况。到此为止,我们就会判定 \(\texttt{2-SAT}\) 的解的存在性问题了。

考虑怎么输出方案。不难发现,如果 \(A\) 所在的强连通分量的拓扑序在 \(\lnot A\) 的之前,就使 \(A\) 为真就行了(可以理解为先到先得),这样就可以给出一组可行解。

Code

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<23],*p1=buf,*p2=buf;
//#define int long long
using namespace std;
template<typename T> void read(T &k)
{
	k=0;T flag=1;char b=getchar();
	while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
	while (isdigit(b)) {k=k*10+b-48;b=getchar();}
	k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=2e6;
int n,m;
struct EDGE{
	int nxt,to;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y) {edge[++tot]=(EDGE){head[x],y};head[x]=tot;}
int dfn[_SIZE+5],bl[_SIZE+5],low[_SIZE+5],cnt;
bool vis[_SIZE+5];
stack<int> s;
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	vis[x]=1,s.push(x);
	for (int i=head[x];i;i=edge[i].nxt)
	{
		int twd=edge[i].to;
		if (!dfn[twd]) tarjan(twd),low[x]=min(low[x],low[twd]);
		else if (vis[twd]) low[x]=min(low[x],dfn[twd]);
	}
	if (low[x]==dfn[x])
	{
		int v;cnt++;
		do{
			v=s.top(),s.pop(),vis[v]=0;
			bl[v]=cnt;
		}while(v!=x);
	}
}
int Num(int x,int flag) {return x+flag*n;}//用于建图计算点,如果值为0则为x,值为1就为x+n
signed main()
{
	read(n),read(m);
	for (int i=1;i<=m;i++)
	{
		int a,b,c,d;read(a),read(b),read(c),read(d);
		AddEdge(Num(a,b),Num(c,!d)),AddEdge(Num(c,d),Num(a,!b));
	}
	for (int i=1;i<=(n<<1);i++) if (!dfn[i]) tarjan(i);
	for (int i=1;i<=n;i++)
		if (bl[i]==bl[i+n]) return puts("IMPOSSIBLE")&0;
	puts("POSSIBLE");
	for (int i=1;i<=n;i++)
		writewith(bl[i]<bl[i+n],' ');
	return 0;
}
posted @ 2022-10-11 11:27  Hanx16Msgr  阅读(25)  评论(0编辑  收藏  举报