【模板】2-sat问题的讨论与分析(包含一些2-sat题目)

本文介绍了2-SAT问题的基本定义,指出它属于图论算法中的问题,并提供了建模和解决方法。通过强连通分量的概念,讨论了解决2-SAT问题的策略。此外,还列举了一些实际问题,如满汉全席、飞机着陆间隔等,展示如何将这些问题转化为2-SAT并求解。文章强调了2-SAT问题的重点在于正确建模和应用。
摘要由CSDN通过智能技术生成

部分内容转自luogu神犇题解:https://www.luogu.org/problemnew/solution/P4782
2-SAT定义:

即给定一些布尔方程,每个变量只能为 t r u e true true or f a l s e false false
要求对这些变量进行赋值,满足布尔方程。
简单来说就是离散数学中的布尔方程例如: ( ¬ a ∨ b ∨ ¬ c ) ∧ ( a ∨ b ∨ ¬ c ) ∧ ( ¬ a ∨ ¬ b ∨ c ) (¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c) (¬ab¬c)(ab¬c)(¬a¬bc)
现在要做的是,为 ABC 三个变量赋值,满足三位学生的要求。
但这是 SAT 问题,已被证明为 NP。
那么 2-SAT 是什么呢?
2-SAT,即只有两个条件,例如布尔方程当中的 c , ¬ c c,\neg c c,¬c 没了,变成了这个样子: ( ¬ a ∨ b ) ∧ ( a ∨ b ) ∧ ( ¬ a ∨ ¬ b ) (\neg a\vee b) \wedge (a\vee b) \wedge (\neg a\vee\neg b) (¬ab)(ab)(¬a¬b)

建图:
x x x y y y至少选一个: ( x , 0 , y , 1 ) , ( y , 0 , x , 1 ) (x,0,y,1),(y,0,x,1) (x,0,y,1),(y,0,x,1)
x x x y y y不能同时选: ( x , 1 , y , 0 ) , ( y , 1 , x , 0 ) (x,1,y,0),(y,1,x,0) (x,1,y,0),(y,1,x,0)
选了 x x x就必须选 y y y ( x , 1 , y , 1 ) , ( y , 0 , x , 0 ) (x,1,y,1),(y,0,x,0) (x,1,y,1),(y,0,x,0)

那么如何解决2-sat问题呢?

用图论中的强连通分量
对于每个变量 x x x,我们建立两个点: ¬ x ¬x ¬x 分别表示变量 x x x 取 true 和取 false。所以,图的节点个数是两倍的变量个数。在存储方式上,可以给第 i i i 个变量标号为 i i i,其对应的反值标号为 i + n i + n i+n。对于每个同学的要求 ( a ∨ b ) (a∨b) (ab),转换为 ¬ a → b ∧ ¬ b → a ¬a→b∧¬b→a ¬ab¬ba。对于这个式子,可以理解为:「若 a 假则 b 必真,若 b 假则 a 必真」然后按照箭头的方向建有向边就好了。综上,我们这样对上面的方程建图.

同一强连通分量内的变量值一定是相等的。也就是说,如果 x 与 ¬ x ¬x ¬x 在同一强连通分量内部,一定无解。反之,就一定有解了。
但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?

这个很简单,只需要 当 x x x 所在的强连通分量的拓扑序在 ¬ x ¬x ¬x 所在的强连通分量的拓扑序之后取 x x x 为真 就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 c o l o r [ x ] < c o l o r [ − x ] color[x] < color[-x] color[x]<color[x]

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e6+5;
int dfn[maxn<<1],low[maxn<<1],sta[maxn<<1],vis[maxn<<1],dfnnum;
int stalen;
int n;
int tag[maxn<<1];
struct Edge{
   
	int u,v,nxt;
}edge[maxn<<1];
int head[maxn<<1],tot;
inline void init(){
   
	memset(head,-1,sizeof(head));
	tot = 0;
}
inline void addedge(int u,int v){
   
	edge[++tot] = {
   u,v,head[u]};
	head[u] = tot;
}
inline void checkadd(int i,int a,int j,int b){
   
	addedge(i+n*(a&1),j+n*(b^1));
	addedge(j+n*(b&1),i+n*(a^1));
}
int num;
void tarjan(int u){
   
	dfn[u] = low[u] = ++dfnnum;
	sta[stalen++] = u,vis[u] = 1;
	for(int i = head[u]; ~i; i = edge[i].nxt){
   
		Edge &e = edge[i];
		if(!dfn[e.v]){
   
			tarjan(e.v);
			low[u] = min(low[u],low[e.v]);
		}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
	}
	if(low[u] == dfn[u]){
   
		num++;	int cur;
		do{
   
			cur = sta[--stalen];
			vis[cur] = 0;
			tag[cur] = num;
		}while(cur!=u);
	}
}
int main()
{
   
	ios::sync_with_stdio(false);
	cin.tie(0);	cout.tie(0);
	init();
	int m;	cin >> n >> m;
	for(int i = 0; i < m; ++i){
   
		int ii,a,j,b;	cin >> ii >> a >> j >> b;
		checkadd(ii,a,j,b);
	}
	for(int i = 1; i <= 2*n; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值