poj 2723 Get Luffy Out 二分答案+2-sat+如何建图

/* 2-sat
题意:m个门,每个门上有两把锁,打开一个就可以通过
2n个钥匙,每两个绑在一起,只能选用一个
问最多可以通过几扇门?


2-sat问题关键在建图,2-sat对每个事物都有两个选项

可以这么建:
每把钥匙有两个状态(用或不用),把这作为2-sat的两个选项
然后是加条件,a、b绑在一起,则选a就不选b,选b就不选a,建边a->!b,b->!a
c、d在同一个门上,则不开c就开d,不开d就开c,建边!c->d,!d->c

然后二分答案都可以了

代码是根据上一题改的,可能有的代码和变量没有用到
*/
#include<stdio.h>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
struct edge
{
	int yong;
	int v[1000000];
	int next[1000000];
	int head[1000000];
	edge()
	{
		clear();
	}
	void clear()
	{
		yong=1;
		memset(head,0,sizeof(head));
	}
	void add(int u,int w)
	{
		v[yong]=w;
		next[yong]=head[u];
		head[u]=yong;
		yong++;
	}
}e1,e2;
int dfn[5000],low[5000];
int n,m,index,scc;
queue<int>q;
stack<int>ss;
int ins[5000],belong[5000],dui[5000],ind[5000],fang[5000];
int a[1100],b[1100];
int c[2500],d[2500];
void tarjan(int u)//tarjan求强连通分量
{
	int i,v;
	dfn[u]=low[u]=index++;
	ins[u]=1;
	ss.push(u);//之前误写成队列
	for(i=e1.head[u];i;i=e1.next[i])
	{
		v=e1.v[i];
		if(dfn[v]==0)
		{
			tarjan(v);
			if(low[v]<low[u])
				low[u]=low[v];
		}else if(ins[v]&&dfn[v]<low[u])
			low[u]=dfn[v];
	}
	if(low[u]==dfn[u])
	{
		do{
			v=ss.top();
			ss.pop();
			ins[v]=0;
			belong[v]=scc;//标记所属强连通分量的标号
		}while(v!=u);
		scc++;
	}
}
int havetry(int mid)
{
	//每次都要从新建图
	e1.clear();
	int i;
	for(i=1;i<=n;i++)
	{
		e1.add(a[i]*2,b[i]*2+1);//a[]b[]c[]d[]里村的都是钥匙编号,因为每个钥匙都有两个状态,*2表示被选状态,*2+1表示不被选状态
		e1.add(b[i]*2,a[i]*2+1);
	}
	for(i=1;i<=mid;i++)
	{
		e1.add(c[i]*2+1,d[i]*2);
		e1.add(d[i]*2+1,c[i]*2);
	}

	index=1;//dfn[]标记
	scc=0;//强连通分量个数,从0开始计数
	memset(dfn,0,sizeof(dfn));
	memset(ins,0,sizeof(ins));
	for(i=0;i<4*n;i++)//有n组钥匙,2*n个钥匙,4*n个钥匙状态
	{
		if(!dfn[i])
			tarjan(i);
	}
	for(i=0;i<2*n;i++)
	{//belong[i]表示i所属的强连通分量的标号
		if(belong[2*i]==belong[2*i+1])//若有夫妇在同一强连通分量中,无解
			break;
	}
	if(i==2*n)
		return 1;
	return 0;
}
int main()
{
	int i,mid,max,min;
	while(scanf("%d%d",&n,&m),n+m)
	{
		//读入数据
		for(i=1;i<=n;i++)
			scanf("%d%d",&a[i],&b[i]);
		for(i=1;i<=m;i++)
			scanf("%d%d",&c[i],&d[i]);
		//二分答案
		min=0;
		max=m+1;
		for(;;)
        {
            mid = (max + min) / 2;
            if(mid == min)
                break;
            if(havetry(mid))
                min = mid;
            else
                max = mid;
        }

		printf("%d\n",mid);
	}
	return 0;
}


第二种建图方法:

/*
也可以这样建图:
每组钥匙有两个选择,把这作为2-sat的两个选择

c、d在同一个门上,则不开c(c和某个钥匙在一组,假设a,不开c,那么就一定开a)就开d,
不开d(d和某个钥匙在一组,假设b,不开d,那么就一定开b)就开c,
建边a->d,b->c

或者这么说  !c<=>a,!c->d,故a->d  !d<=>b,!d->c,故b->c
*/
#include<stdio.h>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
struct edge
{
	int yong;
	int v[1000000];
	int next[1000000];
	int head[1000000];
	edge()
	{
		clear();
	}
	void clear()
	{
		yong=1;
		memset(head,0,sizeof(head));
	}
	void add(int u,int w)
	{
		v[yong]=w;
		next[yong]=head[u];
		head[u]=yong;
		yong++;
	}
}e1,e2;
int dfn[5000],low[5000];
int n,m,index,scc;
queue<int>q;
stack<int>ss;
int ins[5000],belong[5000],dui[5000],ind[5000],fang[5000];
int id[2500];
int c[2500],d[2500];
void tarjan(int u)//tarjan求强连通分量
{
	int i,v;
	dfn[u]=low[u]=index++;
	ins[u]=1;
	ss.push(u);//之前误写成队列
	for(i=e1.head[u];i;i=e1.next[i])
	{
		v=e1.v[i];
		if(dfn[v]==0)
		{
			tarjan(v);
			if(low[v]<low[u])
				low[u]=low[v];
		}else if(ins[v]&&dfn[v]<low[u])
			low[u]=dfn[v];
	}
	if(low[u]==dfn[u])
	{
		do{
			v=ss.top();
			ss.pop();
			ins[v]=0;
			belong[v]=scc;//标记所属强连通分量的标号
		}while(v!=u);
		scc++;
	}
}
int havetry(int mid)
{
	e1.clear();
	int i;
	for(i=1;i<=mid;i++)
	{
		e1.add(c[i]^1,d[i]);//注意这儿啊 a^1表示与a同一组的另外的那个节点,见开头的建图原则
		e1.add(d[i]^1,c[i]);
	}
	
	index=1;//dfn[]标记
	scc=0;//强连通分量个数,从0开始计数
	memset(dfn,0,sizeof(dfn));
	memset(ins,0,sizeof(ins));
	for(i=0;i<2*n;i++)
	{
		if(!dfn[i])
			tarjan(i);
	}
	for(i=0;i<n;i++)
	{//belong[i]表示i所属的强连通分量的标号
		if(belong[2*i]==belong[2*i+1])//若有夫妇在同一强连通分量中,无解
			break;
	}
	if(i==n)
		return 1;
	return 0;
}
int main()
{
	int i,mid,max,min,numed,a,b;
	while(scanf("%d%d",&n,&m),n+m)
	{
		numed=0;
		for(i=1;i<=n;i++)
		{
			scanf("%d%d",&a,&b);//读到的是钥匙的编号,但是进行强连通的时候节点是按组安排在一起的,所以要按节点顺序保存,
			id[a]=numed++;//故用id[i]表示编号为i的钥匙的节点序号
			id[b]=numed++;
		}
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&a,&b);
			c[i]=id[a];//c[]d[]表示的是门上的锁所对应的钥匙的节点序号
			d[i]=id[b];
		}
		//二分答案
		min=0;
		max=m+1;
		for(;;)
		{
			mid = (max + min) / 2;
			if(mid == min)
				break;
			if(havetry(mid))
				min = mid;
			else
				max = mid;
		}
		
		printf("%d\n",mid);
	}
	return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值