题意:有 m 层楼,从一层到 m 层,要进入每层都要打开位于该层的两道门中的至少一道。门锁有 2n 种,每个门锁为 2n 种中的一种,可以重复。有 2n 把钥匙,分别对应 2n 种锁,但钥匙两两一组,共 n 组,每组只能选一个来开门,被选中的可重复使用,另一个不能用。问最多能上多少层。
分析:二分查找能上的层数。每次对于一个确定的层数,也就确定了哪些门需要开。变为一个2-sat问题。其中两两一组的钥匙就是图中的节点。当然图中还需要一些矛盾。矛盾如下,各组的两个钥匙相矛盾(这里无需建边);每层开门钥匙的选择关系里,(first_key && second_key = 0 )
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 11000;
const int inf = 1000000000;
vector<int>edge[maxn];
int n, m, match[maxn], s[maxn], e[maxn];
int tmpdfn, dfn[maxn], low[maxn], inst[maxn], belong[maxn], st[maxn], top, scnt;
void tarjan( int u )
{
int i, v, t, size;
low[u] = dfn[u] = tmpdfn++;
st[top++] = u;
inst[u] = 1;
size = edge[u].size();
for( i = 0; i < size; i++ )
{
v = edge[u][i];
if( dfn[v] == -1 )
{
tarjan( v );
low[u] = min( low[u], low[v] );
}
else if( inst[v] )low[u] = min( low[u], dfn[v] );
}
if( dfn[u] == low[u] )
{
do{ belong[t = st[--top]] = scnt; inst[t] = 0; }while( t != u );
scnt++;
}
}
bool SCC()
{
int i;
top = 0;
tmpdfn = scnt = 1;
memset( dfn, -1, sizeof(dfn) );
memset( inst, 0, sizeof(inst) );
for( i = 1; i <= n * 2; i++ )if( dfn[i] == -1 )tarjan( i );
for( i = 1; i <= n; i++ )if( belong[i] == belong[match[i]] )return false;
return true;
}
void build( int f )
{
int i, j;
for( i = 1; i <= n * 2; i++ )edge[i].clear();
for( i = 1; i <= f; i++ )
{ // it can choose one key each pair but none
edge[s[i]].push_back( match[e[i]] );
edge[e[i]].push_back( match[s[i]] );
}
}
int main()
{
int i, a, b;
while( ~scanf( "%d%d", &n, &m ), n + m )
{
for( i = 1; i <= n; i++ )
{
scanf( "%d%d", &a, &b );
a++; b++;
match[a] = b;
match[b] = a;
}
for( i = 1; i <= m; i++ )
{
scanf( "%d%d", &s[i], &e[i] );
s[i]++; e[i]++;
}
int l, r, mid; // 二分查找
l = 0; r = m + 1; // 1 and m are boundary.
while( l + 1 != r )
{
mid = l + ( r - l ) / 2;
build( mid );
if( SCC() )
l = mid;
else
r = mid;
}
printf( "%d\n", l );
}
return 0;
}