2-SAT:以 POJ 3905 - Perfect Election 为例

首先要感谢:http://blog.csdn.net/pi9nc/article/details/11849843 和 http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html

2-SAT:

1    2  -  SAT就是2判定性问题,是一种特殊的逻辑判定问题。
2    2  -  SAT问题有何特殊性?该如何求解?
3  我们从一道例题来认识2  -  SAT问题,并提出对一类2  -  SAT问题通用的解法。
4 
  Poi   0106   Peaceful Commission [和平委员会]:

某国有n个党派,每个党派在议会中恰有2个代表。
现在要成立和平委员会 ,该会满足:
每个党派在和平委员会中有且只有一个代表 
如果某两个代表不和,则他们不能都属于委员会 
代表的编号从1到2n,编号为2a
  -  1  、2a的代表属于第a个党派
  输入n(党派数),m(不友好对数)及m对两两不和的代表编号 
其中1≤n≤
  8000    0  ≤m ≤  20000   

求和平委员会是否能创立。若能,求一种构成方式。 
输入:     输出:
  3     2           1          
  1     3           4            
  2     4           5                    

  原题可描述为:

有n个组,第i个组里有两个节点Ai, Ai
  '   。需要从每个组中选出一个。而某些点不可以同时选出(称之为不相容)。任务是保证选出的n个点都能两两相容。 
 

(在这里把Ai, Ai
  '   的定义稍稍放宽一些,它们同时表示属于同一个组的两个节点。也就是说,如果我们描述Ai,那么描述这个组的另一个节点就可以用Ai  ' 
  初步构图
如果Ai与Aj不相容,那么如果选择了Ai,必须选择Aj‘ ;同样,如果选择了Aj,就必须选择Ai’ 。
   Ai             Aj '
    Aj             Ai‘                        
这样的两条边对称


  我们从一个例子来看:
假设4个组,不和的代表为:1和4,2和3,7和3,那么构图:
假设:首先选1 3必须选,2不可选 8必须选,
  4  、7不可选   5  、6可以任选一个

 
 矛盾的情况为:
存在Ai,使得Ai既必须被选又不可选。
 
得到算法1:
枚举每一对尚未确定的Ai, Ai‘ ,任选1个,推导出相关的组,若不矛盾,则可选择;否则选另1个,同样推导。若矛盾,问题必定无解。

具体实现:
(1)给每个点设置一个状态V,V=0表示未确定,V=1表示确定选取,V=2表示确定不选取。称一个点是已确定的当且仅当其V值非0。设立两个队列Q1和Q2,分别存放本次尝试选取的点的编号和尝试不选的点的编号。
(2)若图中所有的点均已确定,则找到一组解,结束,否则,将Q1、Q2清空,并任选一个未确定的点i,将i加入队列Q1,将i'加入队列Q2;
(3)找到i的所有后继。对于后继j,若j未确定,则将j加入队列Q1;若j'(这里的j'是指与j在同一对的另一个点)未确定,则将j'加入队列Q2;
(4)遍历Q2中的每个点,找到该点的所有前趋(这里需要先建一个补图),若该前趋未确定,则将其加入队列Q2;
(应该不用建补图吧,直接对Q1中的点进行BFS,原图就可以了,反向是等价的)
(5)在(3)(4)步操作中,出现以下情况之一,则本次尝试失败,否则本次尝试成功:
<1>某个已被加入队列Q1的点被加入队列Q2;
<2>某个已被加入队列Q2的点被加入队列Q1;
<3>某个j的状态为2;
<4>某个i'或j'的状态为1或某个i'或j'的前趋的状态为1;
(6)若本次尝试成功,则将Q1中的所有点的状态改为1,将Q2中所有点的状态改为2,转(2),否则尝试点i',若仍失败则问题无解。
该算法的时间复杂度为O(NM)(最坏情况下要尝试所有的点,每次尝试要遍历所有的边),但是在多数情况下,远远达不到这个上界。
具体实现时,可以用一个数组vst来表示队列Q1和Q2。设立两个标志变量i1和i2(要求对于不同的
i,i1和i2均不同,这样可以避免每次尝试都要初始化一次,节省时间),若vst[i]=i1则表示i已被加入Q1,若vst[i]=i2则表示i已被加入Q2。不过Q1和Q2仍然是要设立的,因为遍历(BFS)的时候需要队列,为了防止重复遍历,加入Q1(或Q2)中的点的vst值必然不等于i1(或i2)。中间一旦发生矛盾,立即中止尝试,宣告失败。

此算法正确性简要说明:
由于Ai,Ai '  都是尚未确定的,它们不与之前的组相关联,前面的选择不会影响Ai, Ai '  。

算法的时间复杂度在最坏的情况下为O(nm)。
在这个算法中,并没有很好的利用图中边的对称性
 更一般的说:
在每个一个环里,任意一个点的选择代表将要选择此环里的每一个点。不妨把环收缩成一个子节点(规定这样的环是极大强连通子图)。新节点的选择表示选择这个节点所对应的环中的每一个节点.(根据图的规则这是显然的)
对于原图中的每条边Ai   ->   Aj(设Ai属于环Si,Aj属于环Sj)如果Si≠Sj,则在新图中连边:Si   -> Sj

这样构造出一个新的有向无环图。
此图与原图等价。
  通过求强连通分量,可以把图转换成新的有向无环图,在这个基础上,介绍一个新的算法。

新算法中,
如果存在一对Ai, Ai ' 属于同一个环 ,则判无解,否则将采用拓扑排序,以自底向上的顺序进行推导,一定能找到可行解。  
 

至于这个算法的得来及正确性,将在下一段文字中进行详细分析。

回忆构图的过程:
对于两个不相容的点 Ai, Aj,构图方式为:Ai
  ->   Aj   '   ,Aj->Ai   '   ,前面提到过,这样的两条边对称,也就是说:
如果存在Ai
  ->   Aj,必定存在Aj   '   ->Ai   '  

等价于:Ai
  ->   Ak,Ak   '   ->Ai   '    方便起见,之后“   ->   ”代表这样一种传递关系.


  猜测1:图中的环分别对称
如果存在Ai,Aj,Ai,Aj属于同一个环(记作Si),那么Ai
  '  , Aj  '  也必定属于一个环(记作Si  '  ). 
 
再根据前面的引理,不难推断出每个环分别对称。 

证明方式与引理相类似
一个稍稍复杂点的结构,其中红、蓝色部分分别为两组对称的链结构
推广2:对于任意一对Si, Si
  '   ,Si的后代节点与Si  '   的前代节点相互对称。 
继而提出:
猜测2:若问题无解,则必然存在Ai, Ai
  '   ,使得Ai,Ai  '  属于同一个环。也就是,如果每一对Ai,Ai  '   都不属于同一个环,问题必定有解。下面给出简略证明: 
  先提出一个跟算法1相似的步骤: 
如果选择Si,那么对于所有Si
  ->  Sj,Sj都必须被选择。 
而Si
  '   必定不可选,这样Si’的所有前代节点也必定不可选(将这一过程称之为删除)。 
 
由推广2可以得到,这样的删除不会导致矛盾。

假设选择S3
  '    
 
选择S3  '  的后代节点, S1  ' 
删除S3
删除S3的前代节点S1
S1与S1
  '  是对称的 
 

每次找到一个未被确定的Si,使得不存在Si
  ->  Si  '   选择Si及其后代节点而删除Si’及Si‘的前代节点。一定可以构造出一组可行解。 
 
因此猜测2成立。

另外,若每次盲目的去找一个未被确定的Si,时间复杂度相当高。
以自底向上的顺序进行选择、删除,这样还可以免去“选择Si的后代节点”这一步。
用拓扑排序实现自底向上的顺序。

一组可能的拓扑序列(自底向上):S1
  '  ,S2,S2  '  ,S3  '  ,S3,S1 
 

算法2的流程: 

  1  .构图
  2  .求图的极大强连通子图
  3  .把每个子图收缩成单个节点,根据原图关系构造一个有向无环图
  4  .判断是否有解,无解则输出(退出)
  5  .对新图进行拓扑排序
  6  .自底向上进行选择、删除
  7  .输出

小结:
整个算法的时间复杂度大概是O(m),解决此问题可以说是相当有效了。
在整个算法的构造、证明中反复提到了一个词:对称。发现、利用了这个图的特殊性质,我们才能够很好的解决问题。
并且,由2
  -  SAT问题模型变换出的类似的题目都可以用上述方法解决。 

全文总结:
充分挖掘图的性质,能够更好的解决问题。
不仅仅是对于图论,这种思想可以在很多问题中得到很好的应用。
希望我们能掌握此种解题的思想,在熟练基础算法的同时深入分析、灵活运用、大胆创新,从而解决更多更新的难题。

Description

In a country (my memory fails to say which), the candidates {1, 2 ..., N} are running in the parliamentary election. An opinion poll asks the question "For any two candidates of your own choice, which election result would make you happy?". The accepted answers are shown in the table below, where the candidates i and j are not necessarily different, i.e. it may happen that i=j. There are M poll answers, some of which may be similar or identical. The problem is to decide whether there can be an election outcome (It may happen that all candidates fail to be elected, or all are elected, or only a part of them are elected. All these are acceptable election outcomes.) that conforms to all M answers. We say that such an election outcome is perfect. The result of the problem is 1 if a perfect election outcome does exist and 0 otherwise.

Input

Each data set corresponds to an instance of the problem and starts with two integral numbers: 1≤N≤1000 and 1≤M≤1000000. The data set continues with M pairs ±i ±j of signed numbers, 1≤i,j≤N. Each pair encodes a poll answer as follows: 

Accepted answers to the poll questionEncoding
I would be happy if at least one from i and j is elected.+i +j
I would be happy if at least one from i and j is not elected.-i -j
I would be happy if i is elected or j is not elected or both events happen.+i -j
I would be happy if i is not elected or j is elected or both events happen.-i +j


The input data are separated by white spaces, terminate with an end of file, and are correct.

Output

For each data set the program prints the result of the encoded election problem. The result, 1 or 0, is printed on the standard output from the beginning of a line. There must be no empty lines on output.

Sample Input

3 3  +1 +2  -1 +2  -1 -3 
2 3  -1 +2  -1 -2  +1 -2 
2 4  -1 +2  -1 -2  +1 -2  +1 +2 
2 8  +1 +2  +2 +1  +1 -2  +1 -2  -2 +1  -1 +1  -2 -2  +1 -1

Sample Output

1
1
0
1

Hint

For the first data set the result of the problem is 1; there are several perfect election outcomes, e.g. 1 is not elected, 2 is elected, 3 is not elected. The result for the second data set is justified by the perfect election outcome: 1 is not elected, 2 is not elected. The result for the third data set is 0. According to the answers -1 +2 and -1 -2 the candidate 1 must not be elected, whereas the answers +1 -2 and +1 +2 say that candidate 1 must be elected. There is no perfect election outcome. For the fourth data set notice that there are similar or identical poll answers and that some answers mention a single candidate. The result is 1.

#include<cstdio>
#include<vector>
#include<queue>
using namespace std;


const int N = 1000 + 10;


//not sure = 0, select = 1, give up = -1
int f[N * 2];
int at[N * 2];
vector<int> G[2 * N];
queue<int> q1, q2;


inline int g(const int x)
{
	return x < 0 ? -2 * x : 2 * x - 1;
}


inline int another(const int x)
{
	return x % 2 == 0 ? x - 1 : x + 1;
}


bool select(const int x)
{
	memset(at, 0, sizeof(at));
	while(!q1.empty())	q1.pop();
	while(!q2.empty())	q2.pop();


	q1.push(x);
	at[x] = 1;
	q2.push(another(x));
	at[another(x)] = 2;
	
	bool res = true;
	
	while(!q1.empty())
	{
		int u = q1.front();
		q1.pop();
		
		//for(int i = 0; i < G[u].size(); i++)
		//{
		for(int i = 0; i < G[u].size(); i++)
		{
			int v = G[u][i];
			if(at[v] == 1)	continue;
			
			if(at[v] == 2 || f[v] == -1)
			{
				res = false;
				break;
			}
			
			at[v] = 1;
			q1.push(v);
			
			at[another(v)] = 2;
			q2.push(another(v));
		}
		
		if(!res)	break;
	}
	
	if(res)
	{
		while(!q2.empty())
		{
			int u = q2.front();
			q2.pop();
			
			f[u] = -1;
			f[another(u)] = 1;
		}
	}
	return res;
}


bool solve(const int n)
{
	memset(f, 0, sizeof(f));
	
	for(int i = 1; i <= n; i++)
	{
		//not sure, bfs from vi
		if(f[g(i)] == 0)
		{
			bool ok = select(g(i));
			if(!ok)	ok = select(g(-i));
			if(!ok)	return false;
		}
	}
	
	return true;
}


int main()
{
	int n, m;
	
	while(scanf("%d%d", &n, &m) != EOF)
	{
		int a, b;
		
		//build
		for(int i = 0; i < m; i++)
		{
			scanf("%d%d", &a, &b);
			G[g(-a)].push_back(g(b));
			G[g(-b)].push_back(g(a));
		}
		
		if(solve(n))	printf("1\n");
		else	printf("0\n");
		
		for(int i = 1; i <= 2 * n; i++)
			while(G[i].size() > 0)	G[i].pop_back();
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值