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