POJ_2723

一开始还是没有抓住2-SAT题目的核心,不会构图。

其实2-SAT最重要的一点就是要找出0-1的关系,即一个东西要么选要么不选,或者要么选这个状态,要么选那个状态,这个物品就应是核心的变量。显然这个题目只有一个物品满足这一点——钥匙,对一把钥匙来讲,我们要么用它要么不用它。

找到核心变量之后,剩下的工作就相对轻松了。首先,对于一对钥匙AB,我们可以得到这样的结论:如果选A,那么一定不选B,如果选B,那么一定不选A,于是就能够连两条边A->~BB->~A。其次,对于能够开一扇门的两把锁CD,我们可以得到这样的结论:如果不用CC锁,那么就必然要用DD锁,如果不用DD锁,那么就一定要用CC锁,于是我们又能够连两条边。

在求解开门数量的时候,我们可以二分枚举开门的数量,然后去找是否存在表示一把钥匙0-1状态的两个点在同一个强连通分量中,也就是去推导在当前开门数量下是否会产生矛盾(即我们既要用这把钥匙同时这把钥匙又不能用)即可。

#include<stdio.h>
#include<string.h>
#define MAXN 6000
#define MAXM 8000
int first[MAXN], next[MAXM], v[MAXM], N, M;
int dfn[MAXN], low[MAXN], cnt, s[MAXN], top, ins[MAXN];
int color[MAXN], col;
int key1[MAXN], key2[MAXN], door1[MAXN], door2[MAXN];
int init()
{
int i;
scanf("%d%d", &N, &M);
if(!N && !M)
return 0;
for(i = 0; i < N; i ++)
scanf("%d%d", &key1[i], &key2[i]);
for(i = 0; i < M; i ++)
scanf("%d%d", &door1[i], &door2[i]);
return 1;
}
void tarjan(int u)
{
int e;
dfn[u] = low[u] = ++ cnt;
for(e = first[u]; e != -1; e = next[e])
{
if(!dfn[v[e]])
{
s[top ++] = v[e];
ins[v[e]] = 1;
tarjan(v[e]);
if(low[v[e]] < low[u])
low[u] = low[v[e]];
}
else if(ins[v[e]] && dfn[v[e]] < low[u])
low[u] = dfn[v[e]];
}
if(low[u] == dfn[u])
{
for(s[top] = -1; s[top] != u;)
{
top --;
ins[s[top]] = 0;
color[s[top]] = col;
}
col ++;
}
}
int com(int mid)
{
int i, j, k, u, e;
memset(first, -1, sizeof(first));
e = 0;
for(i = 0; i < N; i ++)
{
v[e] = 2 * key2[i];
u = 2 * key1[i] + 1;
next[e] = first[u];
first[u] = e;
e ++;

v[e] = 2 * key1[i];
u = 2 * key2[i] + 1;
next[e] = first[u];
first[u] = e;
e ++;
}
for(i = 0; i < mid; i ++)
{
v[e] = 2 * door2[i] + 1;
u = 2 * door1[i];
next[e] = first[u];
first[u] = e;
e ++;

v[e] = 2 * door1[i] + 1;
u = 2 * door2[i];
next[e] = first[u];
first[u] = e;
e ++;
}
cnt = top = col = 0;
memset(dfn, 0, sizeof(dfn));
memset(ins, 0, sizeof(ins));
for(i = 0; i < 2 * N; i ++)
if(!dfn[i])
{
s[top ++] = i;
ins[i] = 1;
tarjan(i);
}
for(i = 0; i < 2 * N; i ++)
if(color[i] == color[i ^ 1])
return 0;
return 1;
}
int main()
{
int max, mid, min;
while(init())
{
min = 0;
max = M + 1;
for(;;)
{
mid = (max + min) / 2;
if(mid == min)
break;
if(com(mid))
min = mid;
else
max = mid;
}
printf("%d\n", mid);
}
return 0;
}


posted on 2011-10-03 14:13  Staginner  阅读(212)  评论(0编辑  收藏  举报