您的位置:首页 > 其它

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

2012-05-26 18:39 369 查看
/* 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;
}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c ini