这是一个很菜的 Oier 的博客|

Hanx16Msgr

园龄:2年8个月粉丝:12关注:3

2022-10-11 11:27阅读: 25评论: 0推荐: 0

2-SAT 学习笔记

2-SAT 学习笔记

有一类生活中的问题:

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

把这个问题抽象一下,有 nbool 变量,有 m 组形似 (a,flaga,b,flagb) 的条件,表示当 a=flaga 时,bflagb,或者当 b=flagb 时,aflaga,要求判断这些条件是否可行,如果可行,给出一种具体赋值方案。

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

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

P4782 【模板】2-SAT 问题

题目描述

n 个布尔变量 x1xn,另有 m 个需要满足的条件,每个条件的形式都是 「xitrue / falsexjtrue / false」。比如 「x1 为真或 x3 为假」、「x7 为假或 x2 为假」。

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

输入格式

第一行两个整数 nm,意义如题面所述。

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

输出格式

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

下一行 n 个整数 x1xnxi{0,1}),表示构造出的解。

样例 #1

样例输入 #1

3 1
1 1 3 0

样例输出 #1

POSSIBLE
0 0 0

提示

1n,m106 , 前 3 个点卡小错误,后面 5 个点卡效率。

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

Solution

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

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

考虑怎么输出方案。不难发现,如果 A 所在的强连通分量的拓扑序在 ¬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 @   Hanx16Msgr  阅读(25)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起