2-sat系列(一) POJ 2723 Get Luffy Out

题意:有2n把钥匙,分成2组,给你每组的钥匙信息,并且每组的钥匙只能用一个。m个门,每个门有2个锁,只要打开一个锁这个门就开了。(顺序遇见m个门)问你最多能够打开多少个门。

建边方式:(需要建2类边,对于每组钥匙(A,B),增加点对(A',B'))

1:n 对钥匙中,A 和 B 只能选择一把,用点A 默示选择钥匙 A ,用 A’ 默示不选择(同理用点B 和B‘ 默示钥匙 B 的选择关系),建边(A -> B‘)默示用钥匙 A 就不用钥匙 B ;还有(B -> A')默示用 B 就不用A。

2:m 道门,每对门都有两把钥匙可以开,假设是A和B ,可能的选择是(用A不消B)或者(用B 不消A),按照这个关系建边(B' -> A),(A’ -> B)

建完边后用2分方法即可求出最多能达到第几扇门

 

2-sat的题目建边是关键,而2-sat建边的原理就是找2个不相容点再进行建边

为什么对门上钥匙建边的时候只能是(A'->B),而不能是(A->B')?

因为建边的时候要求是2个不相容的点对之间建边,而(A,B)不是不相容点对,(A',B')才是不相容点对,不相容点对的意思就是说2个条件不能同时存在,而A,B是可以同时存在的,用A,B同时打开了门上的2把锁,门还是照样能开,相容。而(A',B')表示既不用A钥匙也不用B钥匙,这种情况下门是不能打开的,所以这2个点不能同时存在,同时存在就打不开门,所以为不相容点对,而在建边的时候,每建一条边,如(X->Y)表示选择X后必须选择Y,所以(A'->B)则表示不用A钥匙则必须用B钥匙来开,(B'->A)表示不用B钥匙则必须用A钥匙来。

关于建边的一点点总结

(1)必须要在不相容点对之间建边,如上面的(A',B'),这2个条件同时存在问题就不能解决,就像这题中的如果2把钥匙都不用,则门肯定打不开

(2)建一条边看下,前面是不是一定能推到后面,像本题中的(A'->B)不用A钥匙则必须要用B钥匙,别无选择,不然门就打不开,而(A->B')这种建边则表示用了A钥匙则一定不能用B钥匙,我可以A,B钥匙同时用,如(A->B),前面不一定能推到后面,所以不可行

源代码:
# include<cstdio>
# include<cstring>
# include<vector>
# include<stack>
using namespace std;
# define N 10000
int dfn[N],low[N],instack[N],belong[N],now,cnt,n,m;
stack<int> sta;
vector<int> vec[N];

struct node
{
	int x;
	int y;
}p[N],q[N];

void tarjan(int v)
{
	int i,u,size;
	dfn[v]=low[v]=++now;
	sta.push(v);
	instack[v]=1;
	size=vec[v].size();
	for(i=0;i<size;i++)
	{
		u=vec[v][i];
		if(!dfn[u])
		{
			tarjan(u);
			low[v]=min(low[v],low[u]);
		}
		else if(instack[u])
			low[v]=min(low[v],dfn[u]);
	}
	if(dfn[v]==low[v])
	{
		cnt++;
		while(!sta.empty())
		{
			u=sta.top();
			sta.pop();
			instack[u]=0;
			belong[u]=cnt;
			if(u==v)
				break;
		}
	}
}

int solve(int mid)
{
	int i;
	for(i=0;i<4*n;i++)
		vec[i].clear();
	now=cnt=0;
	memset(instack,0,sizeof(instack));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(belong,0,sizeof(belong));
	for(i=0;i<n;i++)
	{
		vec[2*p[i].x].push_back(2*p[i].y+1);
		vec[2*p[i].y].push_back(2*p[i].x+1);
	}
	for(i=0;i<mid;i++)
	{
		vec[2*q[i].x+1].push_back(2*q[i].y);
		vec[2*q[i].y+1].push_back(2*q[i].x);
	}
	for(i=0;i<4*n;i++)
		if(!dfn[i])
			tarjan(i);
	for(i=0;i<2*n;i+=2)
		if(belong[i]==belong[i+1])
			return 0;
	return 1;
}

int ans()
{
	int l=0,r=m,mid,ans;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(solve(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	return ans;
}

int main()
{
	int i;
	while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
	{
		for(i=0;i<n;i++)
			scanf("%d%d",&p[i].x,&p[i].y);
		for(i=0;i<m;i++)
			scanf("%d%d",&q[i].x,&q[i].y);
		printf("%d\n",ans());
	}
	return 0;
}
最后,我们再增加点步骤看看经过经过前面几道门分别需要哪些钥匙,即看看最后的结果

建立反向图-->拓扑排序(着色)-->输出结果

2-sat系列(一) POJ 2723 Get Luffy Out - rdy - rdy
 
 结果显示:通过第一扇门需要钥匙0和钥匙1,通过前2扇门只要钥匙0,通过前3扇门只要钥匙0和4......
根据结果也可以想想你建图的思路是否正确!
贴下显示结果源代码,技术不够,写的代码比较长,嘿嘿~~~

# include<cstdio>
# include<cstring>
# include<vector>
# include<stack>
# include<queue>
using namespace std;
# define N 10000
int dfn[N],low[N],instack[N],belong[N],color[N],in[N],opp[N],now,cnt,n,m;
stack<int> sta;
vector<int> vec[N],dag[N];
queue<int> que;

struct node
{
 int x;
 int y;
}p[N],q[N];
 
void tarjan(int v)
{
	int i,u,size;
	dfn[v]=low[v]=++now;
	sta.push(v);
	instack[v]=1;
	size=vec[v].size();
	for(i=0;i<size;i++)
	{
		u=vec[v][i];
		if(!dfn[u])
		{
			tarjan(u);
			low[v]=min(low[v],low[u]);
		}
		else if(instack[u])
			low[v]=min(low[v],dfn[u]);
	}
	if(dfn[v]==low[v])
	{
		cnt++;
		while(!sta.empty())
		{
			u=sta.top();
			sta.pop();
			instack[u]=0;
			belong[u]=cnt;
			if(u==v)
				break;
		}
	}
}
 
void buliddag()         //建立反向图
{
	int i,v,u;
	for(v=0;v<4*n;v++)
	{
		for(i=0;i<vec[v].size();i++)
		{
			u=vec[v][i];
			if(belong[u]!=belong[v])
			{
				dag[belong[u]].push_back(belong[v]);
				in[belong[v]]++;
			}
		}
	}
}
 
void topsort()     //拓扑排序,着色
{
	int i,v,u;
	for(i=1;i<=cnt;i++)
	if(in[i]==0)
		que.push(i);
	while(!que.empty())
	{
		u=que.front();
		que.pop();
		if(!color[u])
		{
			color[u]=1;
			color[opp[u]]=2;
		}
		for(i=0;i<dag[u].size();i++)
		{
			v=dag[u][i];
			in[v]--;
			if(in[v]==0)
				que.push(v);
		}
	}
}
 
int solve(int mid)
{
	int i;
	for(i=0;i<4*n;i++)
	{
		vec[i].clear();
		dag[i].clear();
	}
	now=cnt=0;
	memset(instack,0,sizeof(instack));
	memset(color,0,sizeof(color));
	memset(in,0,sizeof(in));
	memset(opp,0,sizeof(opp));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(belong,0,sizeof(belong));
	for(i=0;i<n;i++)
	{
		vec[2*p[i].x].push_back(2*p[i].y+1);
		vec[2*p[i].y].push_back(2*p[i].x+1);
	}
	for(i=0;i<mid;i++)
	{
		vec[2*q[i].x+1].push_back(2*q[i].y);
		vec[2*q[i].y+1].push_back(2*q[i].x);
	}
	for(i=0;i<4*n;i++)
		if(!dfn[i])
			tarjan(i);
	for(i=0;i<4*n;i+=2)
	{
		if(belong[i]==belong[i+1])
		{
			printf("The %d door can't go on!\n",mid);
			return 0;
		}
		else
		{
			opp[belong[i]]=belong[i+1];
			opp[belong[i+1]]=belong[i];
		}
	}
	buliddag();
	topsort();
	printf("Pass the %d doors,you should use: ",mid);
	for(i=0;i<4*n;i+=2)
	{
		if(color[belong[i]]==1)
			printf("key(%d) ",i/2);
	}
	printf("\n");
	return 1;
}
 
int ans()
{
	int l=0,r=m,mid,ans;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(solve(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	return ans;
}
 
int main()
{
 int i;
 while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
 {
	for(i=0;i<n;i++)
		scanf("%d%d",&p[i].x,&p[i].y);
	for(i=0;i<m;i++)
		scanf("%d%d",&q[i].x,&q[i].y);
	for(i=1;i<=m;i++)
		if(!solve(i))
			break;
 // printf("%d\n",ans());
 }
 return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值