2-sat学习笔记

2- sat 问题

我笑笑,np完全,弹指一挥间罢了

正文

定义

2-SAT就是2判定性问题,是一种特殊的逻辑判定问题。

我们先来看看什么2-sat,问题,他大概可以理解为,给你一堆bool型变量,每个变量可能为真或假,现在有一种限制关系指

假如(xi)变量选了什么,(yi)只能是什么。我们称变量只有两种可能性的叫2-sat问题,而3-sat或更高的sat不行,因为他们是NP完全的。

我们通过建图来操作2-sat问题

我们来看一个实际的题来说明

eg和平委员会

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

我们连边的原则是

假设我们有一个关系,选了x不能y,那么选了x是y那一列只能选y',而选y了则x那一列只能选x'

于是我们就从建两条边,x->y',y->x'

我们的如图,看起来很对称,于是我们的箭头关系是选了u就要选v,于是我们进行缩点,对于之后的图做拓扑序,每一个对应中,选拓扑序前的

于是我们,有了一版代码。。。

在这里注意要做一个,强连通分量中的对应关系

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int Maxm=50000,Maxn=16008;
struct Node{
	int fr,to,lac;
}edge[Maxm],vedge[Maxm];
int vcnt,vh[Maxn],num,col[Maxn],p[Maxn],x,y,cnt,n,m,dfn[Maxn],low[Maxn],dep,sta[Maxn],top,h[Maxn],b[Maxn],a[Maxn];
bool fsta[Maxn],f,flag[Maxn];
void insert(int x,int y){//表示x,y互相仇视 
	int u=(y^1);
	edge[cnt].to=u;
	edge[cnt].fr=x;
	edge[cnt].lac=h[x];
	h[x]=cnt++;
	u=(x^1);
	edge[cnt].to=u;
	edge[cnt].fr=y;
	edge[cnt].lac=h[y];
	h[y]=cnt++;
}
void tarjan(int u){
	dfn[u]=low[u]=++dep;
	fsta[u]=1;sta[++top]=u;
	for(int i=h[u];i!=-1;i=edge[i].lac){
		int to=edge[i].to;
		if(dfn[to]){
			if(fsta[to]) low[u]=min(low[u],dfn[to]);
			continue;
		}
		tarjan(to);
		low[u]=min(low[u],low[to]);
	}
	if(low[u]==dfn[u]){
		num++;
		while(fsta[u]){
			fsta[sta[top]]=0;
			col[sta[top]]=num;
			top--;
		}
	}
}
void vinsert(int x,int y){
	vedge[vcnt].fr=x;
	vedge[vcnt].to=y;
	vedge[vcnt].lac=vh[x];
	vh[x]=vcnt++;
}
int main(){
//	freopen("sp.in","r",stdin);
	scanf("%d%d",&n,&m);
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		insert(--x,--y);
	}
	for(int i=0;i<2*n;i++) if(!dfn[i]) tarjan(i);
	for(int i=0;i<2*n;i+=2)
		if(col[i]==col[i^1]){
			printf("NIE");//判断无解 
			return 0;
		}
	for(int i=0;i<2*n;i++) p[col[i]]=col[i^1];//求其对应 
	memset(vh,-1,sizeof vh);
	for(int i=0;i<cnt;i++){
		if(col[edge[i].fr]!=col[edge[i].to]){
			vinsert(col[edge[i].to],col[edge[i].fr]);
			b[col[edge[i].fr]]++;
		}
	}
	int ans=0;
//	朴素 
	/*for(int k=1;k<=num/2;k++){
		int v=-1;
		for(int i=1;i<=num;i++){
			if(b[i]==0&&!flag[i]){
				v=i;
				break;
			}
		}
		if(v==-1) break;
		flag[v]=1;
		flag[p[v]]=1;
		for(int i=0;i<2*n;i++){
			if(col[i]==v){
				a[++ans]=i;
			}
		}
		for(int i=vh[v];i!=-1;i=vedge[i].lac)
			b[vedge[i].to]--; 
	}
	*/ 
	queue<int> q1;
	for(int i=1;i<=num;i++) if(b[i]==0) q1.push(i);
	while(!q1.empty()){
		int v=q1.front();
		q1.pop();
		if(!flag[v]){
			for(int i=0;i<2*n;i++){
				if(col[i]==v){
					a[++ans]=i;
				}
			}
			for(int i=vh[v];i!=-1;i=vedge[i].lac){
				b[vedge[i].to]--; 
				if(b[vedge[i].to]==0) q1.push(vedge[i].to);
			}
		}
		flag[v]=1;
		flag[p[v]]=1;
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=ans;i++){
		printf("%d
",a[i]+1);
	} 
	return 0;
}

这是朴素的(n^2)拓扑,我们考虑优化它,可以用队列,这里就不贴代码了,

事实上,我们在进行tarjan是其实相当与做了topo,我们考虑下面的输入

2 2
1 3
1 4

这样能看出.事实上i是可以指向i'的而对于tarjan我们i'的col一定比i的col小

所以我们考虑输出i和i'中小的那个

for(int i=0;i<2*n;i+=2){
	f(col[i]<col[i^1]) printf("%d
",i+1);
	else printf("%d
",i+2);
}

好了就这样吧,后面我会补充满汉这个题的。 于2020.2.6 0:41

eg JSOI 满汉全席

这题,水呀。。。。。裸的2-sat

但是要注意存在输出GOOD。。。太坑了

就这样吧,没甚么好说的

我有可能后面补一下塔防,BJOI的,但是
看时间吧,毕竟假期不多了

2-sat,不过是两个星期六

我困了,两周之内做第二版

原文地址:https://www.cnblogs.com/zhltao/p/12267465.html