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\n",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\n",i+1);
	else printf("%d\n",i+2);
}

好了就這樣吧,后面我會補充滿漢這個題的。 於2020.2.6 0:41

eg JSOI 滿漢全席

這題,水呀。。。。。裸的2-sat

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

就這樣吧,沒甚么好說的

我有可能后面補一下塔防,BJOI的,但是
看時間吧,畢竟假期不多了

2-sat,不過是兩個星期六

我困了,兩周之內做第二版


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM