2-SAT

定义

SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适
定性问题,简称 k-SAT。而当 k > 2 时该问题为 NP 完全的。
所以我们之研究 k = 2 的情况。

2-SAT,简单的说就是给出 n 个集合,每个集合有两个元素,已知若
干个 < a,b > ,表示 a 与 b 矛盾(其中 a 与 b 属于不同的集
合)。然后从每个集合选择一个元素,判断能否一共选 n 个两两不
矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种
即可。

常用解决方法

TarjanSCC 缩点

算法考究在建图这点,我们举个例子来讲:
假设有 a1,a2 和 b1,b2 两对,已知 a1 和 b2 间有矛盾,于是为了
方案自洽,由于两者中必须选一个,所以我们就要拉两条条有向边
(a1,b1) 和 (b2,a2) 表示选了 a1 则必须选 b1 ,选了 b2 则必须选
a2 才能够自洽。
然后通过这样子建边我们跑一遍 Tarjan SCC 判断是否有一个集合中
的两个元素在同一个 SCC 中,若有则输出不可能,否则输出方案。
构造方案只需要把几个不矛盾的 SCC 拼起来就好了。
输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果
变量 ¬x 的拓扑序在 x 之后,那么取 x 值为真。应用到 Tarjan 算法
的缩点,即 x 所在 SCC 编号在 ¬x 之前时,取 x 为真。因为 Tarjan
算法求强连通分量时使用了栈,所以 Tarjan 求得的 SCC 编号相当于
反拓扑序。
显然地,时间复杂度为 O(n + m) 。

暴搜及模板

就是沿着图上一条路径,如果一个点被选择了,那么这条路径以后
的点都将被选择,那么,出现不可行的情况就是,存在一个集合中
两者都被选择了。
那么,我们只需要枚举一下就可以了,数据不大,答案总是可以出
来的。

// 来源:刘汝佳白书第 323 页
struct Twosat {
int n;
vector<int> g[maxn * 2];
bool mark[maxn * 2];
int s[maxn * 2], c;
bool dfs(int x) {
if (mark[x ^ 1]) return false;
if (mark[x]) return true;
mark[x] = true;
s[c++] = x;
for (int i = 0; i < (int)g[x].size(); i++)
if (!dfs(g[x][i])) return false;
return true;
}
void init(int n) {
this->n = n;
for (int i = 0; i < n * 2; i++) g[i].clear();
memset(mark, 0, sizeof(mark));
}
void add_clause(int x, int y) { // 这个函数随题意变化
g[x].push_back(y ^ 1); // 选了 x 就必须选 y^1
g[y].push_back(x ^ 1);
}
bool solve() {
for (int i = 0; i < n * 2; i += 2)
if (!mark[i] && !mark[i + 1]) {
c = 0;
if (!dfs(i)) {
while (c > 0) mark[s[--c]] = false;
if (!dfs(i + 1)) return false;
}
}
return true;
}
};

例题

1、 Party

有n对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有1人可以列席。在2n 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的2个人是不会同时出现在聚会上的。有没有可能会有n 个人同时列席?
Input
n: 表示有n对夫妻被邀请 (n<= 1000)
m: 表示有m 对矛盾关系 ( m < (n - 1) * (n -1))

在接下来的m行中,每行会有4个数字,分别是 A1,A2,C1,C2
A1,A2分别表示是夫妻的编号
C1,C2 表示是妻子还是丈夫 ,0表示妻子 ,1是丈夫
夫妻编号从 0 到 n -1
Output
如果存在一种情况 则输出YES
否则输出 NO
Sample Input
2
1
0 1 1 1
Sample Output
YES

#include<iostream>
#include<vector>
#include<string>
#include<stdio.h>
#include<cstring>
using namespace std;

const int maxn=1000+10;
struct Twosat
{
	int n;
	vector<int> g[maxn*2];
	int s[maxn*2],c;
	bool mark[maxn*2];
	bool dfs(int x){
		if(mark[x^1])//此条件的反存在,不成立
		return false;
		if(mark[x])//当此条件存在,则成立
		return true;
		mark[x]=true;//假设此条件成立
		s[c++]=x;//加入假设的条件
		for(int i=0;i<(int)g[x].size();i++){//此条件能推出的各种条件
			if(!dfs(g[x][i]))return false;
		}
		return true;
	}
	void init(int n)
	{
		this->n=n;
		for(int i=0;i<n*2;i++)
		g[i].clear();
		memset(mark,0,sizeof(mark)); 
	}
//	void(add_clause(int x,int y))
//	{
//		g[x].push_back(y^1);//选x必须选y^1 
//		g[y].push_back(x^1);
//	}
	void add_clause(int x,int xval,int y,int yval)
    {
        x=x*2+xval;
        y=y*2+yval;
        g[x].push_back(y);
    }
	bool solve()
	{
		for(int i=0;i<n*2;i+=2)
		{
			if(!mark[i]&&!mark[i+1])
			{
				c=0;
				if(!dfs(i))
				{
					while(c>0)mark[s[--c]]=false;//因为在深搜的时候加入了假设条件
					if(!dfs(i+1))return false;
				}
			}
		}
		return true;
	}
}Ts;
 
 
 int main(){
 	int n,m;
 	while(scanf("%d%d",&n,&m)==2)
    {
        Ts.init(n);
        while(m--)
        {
            int a,b,va,vb;
            scanf("%d%d%d%d",&a,&b,&va,&vb);
            Ts.add_clause(a,va,b,vb^1);
            Ts.add_clause(b,vb,a,va^1);//因为关系矛盾是双方的
        }
        printf("%s\n",Ts.solve()?"YES":"NO");
    } 	
	 return 0;
 } 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值