2_SAT

2_SAT
有N个变量,每个变量只有两种可能的取值。再给定M个条件,每个条件都是对1两个变量的取值限制。求是否存在N个变量的合法赋值,使M个条件均被满足。
我们可以把此类问题转换成统一的形式:若变量 A i A_i Ai赋值为 A i p A_ip Aip,则变量 A j A_j Aj必须赋值为 A j q A_jq Ajq,其中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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值