2_SAT

2_SAT
有N個變量,每個變量只有兩種可能的取值。再給定M個條件,每個條件都是對1兩個變量的取值限制。求是否存在N個變量的合法賦值,使M個條件均被滿足。
我們可以把此類問題轉換成統一的形式:若變量AiA_i賦值爲AipA_ip,則變量AjA_j必須賦值爲AjqA_jq,其中q,p爲{0,1};

解:
首先,對於這張圖中的每個強連通分量中的點一定要麼同時選,要麼同時不選。
那麼,我們將圖縮強連通分量。
這時候就可以判斷無解了。如果xi,0和xi,1在同一個強連通分量中,那麼明顯無解。
此時,你就得到了一張拓撲圖。這張拓撲圖中,如果u可以到達v,那麼u選擇則v也必須選擇。
那麼,你可以利用強連通分量的標號來得到反向的拓撲序

for (int i = 1; i <= n; ++i)
    print((color[i] < color[i + n])), putchar(' '); //注意大小號
puts("");

注意點:
對於建圖方式,可以是 i 對於 i+n ,或者是 i 對應 i^1 (i+1)。
逆否命題不一定成對出現。

尤其注意特殊的連邊方式,在必定的條件,即類似 a && b == 1,則a和b都必須爲1,那麼需要添加兩條邊a->a,b->b。(在此情況下,i必定是a,j必定是b)
邏輯上來說就是如果2發生則1必須會發生(由於1和2是對立事件,也就是說選擇2的話會產生矛盾)。但是由於1並不能推出2,所以第一組仍然符合2-sat。這個時候如果規定在第一組中必須選擇2。也就是加一條1–>2的邊後就會使得1和2處於同一個強連通分量中,被判定無解。

模板

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+7,M = 2e6+7;
int n,m;
int cnt,head[N],ver[M],nex[M];
void add(int x,int y){
	nex[++cnt] = head[x];
	ver[cnt] = y;
	head[x] = cnt;
}
int col,dfstime;
int dfn[N],low[N],id[N],all[N];//id是用來記錄第i個點的顏色,也可以用作判斷是否在棧中 
stack<int> st;
void tarjan(int x){
	dfn[x] = low[x] = ++dfstime;
	st.push(x);
	for(int i=head[x];i;i=nex[i]){
		int y =ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}else if(!id[y])  //判斷是否在棧中 
		low[x] = min(low[x],dfn[y]);
	}
	int i;
	if(low[x] == dfn[x]){
		++col;	
		do{
			i = st.top();st.pop();
			id[i] = col;
		}while(i!=x);
	}
} 

void read(){
	scanf("%d%d",&n,&m);
	for(int i=1; i <= m;i++){
		int x,a,y,b;
		scanf("%d%d%d%d",&x,&a,&y,&b);
		add(x+(1-a)*n,y+b*n); //連邊(非a,b) 
		add(y+(1-b)*n,x+a*n); //連邊(非b,a)
	}
}
int main(){
	read();
	for(int i=1;i <= 2*n;i++)
	if(!dfn[i]) tarjan(i);
	
	for(int i=1;i<=n;i++)
	if(id[i] == id[i+n]){
		puts("IMPOSSIBLE");return 0;
	}
	puts("POSSIBLE");
	for(int i=1;i<=n;i++)
	printf("%d ",id[i] > id[i+n]);
	return 0;
} 

出現必定的情況:
poj 2296

#include <iostream>
#include <cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 2e4+7;
int n;
int x[N],y[N];
int col,dfstime;
int dfn[N],low[N],id[N],all[N];
int cnt,head[N],nex[N],ver[N];
stack<int> st;
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void init(){
	for(int i = 0; i < N ;i++)
	dfn[i] = low[i] = id[i] = all[i] = 0,head[i] = -1;
	while(st.size()) st.pop();
	col = dfstime = 0; 
	cnt = 0;
}
void read(){
	for(int i = 0; i < N ;i ++)
	x[i] = y[i] =0;
		scanf("%d",&n);
		for(int i = 1; i <= n; i++){
		scanf("%d%d",&x[i],&y[i]);
	}
}
void pre(int d){
	int maxs,mins;
	for(int i=1; i <= n;i++){
		for(int j = 1;j < i;j++){
			if(abs(x[i] - x[j]) < d){
				if(y[i] > y[j]) maxs = i,mins = j;
				else maxs = j,mins = i;	
				if(y[maxs] == y[mins]){
					add(maxs+n,mins),add(mins,maxs+n);
					add(maxs,mins+n),add(mins+n,maxs);
				}
				else if(y[maxs] - y[mins] < 2*d){
					if(y[maxs] - y[mins] < d)//此時一個必定向上,一個必定向下
					add(maxs,maxs+n),add(mins+n,mins);//避免最後出現maxs和maxs+n在同一邏輯下但不在同一分量中的情況。 
					else{
						add(maxs,mins),add(mins,maxs);
						add(mins+n,maxs+n),add(mins+n,maxs+n);
					}
				}
			}
		}
	}
}
void tarjan(int x){
	dfn[x] = low[x] = ++ dfstime;
	st.push(x);
	for(int i = head[x] ; ~i; i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}else if(!id[y])
		low[x] = min(low[x],dfn[y]);
	}
	int i;
	if(low[x] == dfn[x]){
		++col;
		do{
			i = st.top();st.pop();
			id[i] = col;
			all[col] ++;
		}while(i != x);
	}
}
bool solve(int d){
	init();
	pre(d);
	for(int i=1; i <= 2*n ;i++)
	if(!dfn[i]) tarjan(i);
	
	for(int i=1;i <= n;i++){
		if(id[i] == id[i+n]){
		return false;			
		}

	}
	return true;
}
int main(){
	int T; scanf("%d",&T);
	while(T--){
		read();
		int l = 0, r = 20010,ans=-1;
		while(l <= r){
			int mid = (l+r)>>1;
			if(solve(mid)){
				ans = mid,l = mid+1;
			}else r = mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
} 

不同的建圖方式:
poj 3683

const int N = 4010,M=N*N;
int n;
int cnt=1,head[N],ver[M],nex[M];
int col,dfstime;
int s[N] ,t[N];
int dfn[N],low[N],id[N];//id是用來記錄第i個點的顏色,也可以用作判斷是否在棧中 
stack<int> st;
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void tarjan(int x){
	dfn[x] = low[x] = ++dfstime;
	st.push(x);
	for(int i=head[x]; ~i ; i=nex[i]){
		int y =ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}else if(!id[y])  //判斷是否在棧中 
		low[x] = min(low[x],dfn[y]);
	}
	int i;
	if(low[x] == dfn[x]){
		++col;	
		do{
			i = st.top();st.pop();
			id[i] = col;
		}while(i!=x);
	}
} 
int solve()
{
    while(st.size()) st.pop();
   	col=dfstime = 0;
    for (int i=0; i<2*n; i++)
    {
        if (!dfn[i])
            tarjan(i);
    }
    for (int i=0;i<n;i++)//不在一個聯通塊裏
    {
        if (id[i]==id[i^1]) return 0;
    }
    return 1;
}
bool against(int x,int y)
{
    if((t[x]<=s[y])||(s[x]>=t[y])) return 0;
    else return 1;
}
int main()
{
    while (scanf ("%d",&n)!=EOF)
    {
    	for(int i=0;i<N;i++) head[i] = -1,dfn[i]=low[i]=id[i]=0;
    	cnt = 1;
        for (int i=0;i<n*2;i+=2)
        {
            int h1,m1,h2,m2,tmp;
            scanf ("%d:%d%d:%d%d",&h1,&m1,&h2,&m2,&tmp);
            s[i]=h1*60+m1;
            t[i]=h1*60+m1+tmp;

            s[i^1]=h2*60+m2-tmp;
            t[i^1]=h2*60+m2;
        }
        memset(head,-1,sizeof(head));
       for (int i=0;i<2*n;i++) //
        {
            for (int j=i+1;j<2*n;j++) //
            {
            	if (i!=(j^1)&&against(i,j)) //
                {
                    add(i,j^1); //
                    add(j,i^1); //
                }
            }
        }
        if (!solve()) printf ("NO\n");
        else
        {
            printf ("YES\n");
            for (int i=0;i<2*n;i+=2)
            {
              if (id[i]<id[i^1]) //
                {
                    printf ("%02d:%02d %02d:%02d\n",s[i]/60,s[i]%60,t[i]/60,t[i]%60);
                }
                else
                    printf ("%02d:%02d %02d:%02d\n",s[i^1]/60,s[i^1]%60,t[i^1]/60,t[i^1]%60);  //              
            }
        }
    }
    return 0;
}
發佈了88 篇原創文章 · 獲贊 18 · 訪問量 8550
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章