poj 2723 2-SAT(锁开钥匙救人)

题意:某人要去一个地方救朋友。已知他有n对钥匙,一共2n把。要求每对钥匙只能选一把来用。有m个门,每个门有2把锁,只要打开其中的一把锁就能打开这个门。见到门的顺序和输入一致。给定钥匙信息以及每扇门上的两把锁的信息,问如何选择钥匙能够打开最多的门。

思路:因为有2n把不同钥匙,所以图的节点有4n个。如果输入的钥匙信息为(a,b),那么添加边a->b+2n;b->a+2n。表示如果带上了a钥匙,那么一定不能带b;反之亦然;

对于给出的门的信息(a,b)这样添加边:a+2n->b;b+2n->a。表示如果没有带上a钥匙,那么一定要带上b钥匙;反之亦然。接着就是建图tarjan,二分答案……

#include <stdio.h>
#include <string.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define clc(a) memset(a,0,sizeof(a))
#define N 1030
#define M 2050
struct pair{
	int a,b;
}key[N],door[M];
struct edge{
	int y,next;
}e[(N+M)<<1];
int first[4*N],strong[4*N],dfn[4*N],low[4*N],stack[4*N];
int n,m,top,tops,id,num;
void init(){
	top = id = num = 0;
	tops = -1;
	clc(strong);
	memset(first,-1,sizeof(first));
	memset(dfn,-1,sizeof(dfn));
}
void add(int x,int y){
	e[top].y  = y;
	e[top].next = first[x];
	first[x] = top++;
}
void tarjan(int x){
	int i,y;
	dfn[x] = low[x] = ++id;
	stack[++tops] = x;
	for(i = first[x];i!=-1;i=e[i].next){
		y = e[i].y;
		if(dfn[y] == -1){
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}else if(!strong[y])
			low[x] = min(low[x],dfn[y]);
	}
	if(dfn[x] == low[x]){
		num++;
		do{
			strong[stack[tops]] = num;
		}while(stack[tops--]!=x);
	}
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d %d",&n,&m)&&n&&m){
		int i,lo,high,mid,res;
		for(i = 0;i<n;i++)
			scanf("%d %d",&key[i].a,&key[i].b);
		for(i = 0;i<m;i++)
			scanf("%d %d",&door[i].a,&door[i].b);

		lo = 1;
		high = m;
		while(lo <= high){
			mid = (lo+high)>>1;
			init();
			for(i = 0;i<n;i++){
				add(key[i].a,key[i].b+2*n);
				add(key[i].b,key[i].a+2*n);
			}
			for(i = 0;i<mid;i++){
				add(door[i].a+2*n,door[i].b);
				add(door[i].b+2*n,door[i].a);
			}
			for(i = 0;i<4*n;i++)
				if(dfn[i] == -1)
					tarjan(i);
			for(i = 0;i<2*n;i++)
				if(strong[i] == strong[i+2*n])
					break;
			if(i >= 2*n){
				res = mid;
				lo = mid+1;
			}else
				high = mid-1;
		}
		printf("%d\n",res);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值