poj 2723 二分+2-sat判定

时间:2023-03-09 00:31:35
poj 2723 二分+2-sat判定

题意:给出n对钥匙,每对钥匙只能选其中一个,在给出每层门需要的两个钥匙,只要一个钥匙就能开门,问最多能到哪层。

思路:了解了2-SAT判定的问题之后主要就是建图的问题了,这里建图就是对于2*n个钥匙,分别分成a和a'两组,即选了比如a,b一组钥匙,选择了a则必须选择b',那么进行连边,而对于每层门,若该门能开,且选择了a',则b肯定需要选,那么a'和b连边。最后就是判定了,同样的 若a与a'在同一个连通分量中,表示无解。这题需要利用二分枚举答案m。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
#define MAXN 5005
#define MAXM 50005
#define inf 100000000
using namespace std;
struct Edge
{
int v,next;
}edge[MAXM];
int n,m,tot;
int top,scnt,index;
int low[MAXN],dfn[MAXN],instack[MAXN],head[MAXN];
int stack[MAXN],fa[MAXN],a[MAXN],b[MAXN],hash[MAXN];
int x[MAXN],y[MAXN];
void init()
{
top=scnt=index=tot=0;
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(instack,0,sizeof(instack));
}
void addedge(int u,int v)
{
edge[tot].v=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void tarjan(int u)
{
low[u]=dfn[u]=++index;
instack[u]=1;
stack[++top]=u;
int v;
for(int i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
scnt++;
do
{
v=stack[top--];
instack[v]=0;
fa[v]=scnt;
}while(v!=u);
}
}
bool check(int n)
{
for(int i=1;i<=n;i++)
{
if(fa[i]==fa[i+n]) return false;
}
return true;
}
void build(int bound)
{
init();
for(int i=1;i<=n;i++)
{
addedge(a[i],b[i]+2*n);
addedge(b[i],a[i]+2*n);
}
for(int i=1;i<=bound;i++)
{
addedge(x[i]+2*n,y[i]);
addedge(y[i]+2*n,x[i]);
}
for(int i=1;i<=2*n;i++)
{
if(!dfn[i])tarjan(i);
}
}
void solve()
{
int ans=0;
int low=0,high=m;
while(low<=high)
{
int mid=(low+high)>>1;
build(mid);
if(check(2*n))ans=max(ans,mid),low=mid+1;
else high=mid-1;
}
printf("%d\n",ans);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)break;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
a[i]++;b[i]++;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x[i],&y[i]);
x[i]++;y[i]++;
}
solve();
}
return 0;
}