HDU 1816 Get Luffy Out *(2-SAT)
http://acm.hdu.edu.cn/showproblem.php?pid=1816
题意:
你有2N把钥匙,你的前面按顺序有M个门,每个门有两个锁,现在你必须用这2N把钥匙去开门. 一个门只要一把锁被打开了这个门就可以被打开.且2N把钥匙还有N个配对关系,每一对的两把钥匙i与j,如果用了i钥匙,那么j钥匙永远不能再用.如果用了j钥匙,那么i钥匙永远不能在用.(这里一把钥匙i可能属于多个配对关系,而POJ2723中2N把钥匙是正好被分成了N对,每把钥匙只出现1次)
分析: 本题类似POJ2723:
http://blog.csdn.net/u013480600/article/details/34891151
由于POJ我没用二分做,所以这里我用二分再做一次.
对于2-SAT的图,有两类关系我们需要添加边.
第一类,对于a与b钥匙配对,那么a与b不能同时出现,有:
add(a,0,b,1) add(b,0,a,1) 这里a=0 表示用a钥匙.
第二类,对于门上有锁a与b,那么有:
add(a,1,b,0) add(b,1,a,0) a=1 表示不用钥匙a
其实该题直接用POJ2723的代码就能AC,刘汝佳的模板不用修改,可能用tarjan算法需要修改些东西吧?
AC代码: 二分125ms,比直接遍历速度(406ms)要快不少
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2000+50;
int n,m;
int a[maxn],b[maxn];//每对钥匙
int c[maxn],d[maxn];//每个门上的锁对
struct TwoSAT
{
int n;
vector<int> G[maxn*2];
int S[maxn*2],c;
bool mark[maxn*2];
bool dfs(int x)
{
if(mark[x^1]) return false;
if(mark[x]) return true;
mark[x]=true;
S[c++]=x;
for(int i=0;i<G[x].size();i++)
if(!dfs(G[x][i])) return false;
return true;
}
void init(int n)
{
this->n=n;
for(int i=0;i<2*n;i++) G[i].clear();
memset(mark,0,sizeof(mark));
}
void add_clause(int x,int xval,int y,int yval)
{
x=x*2+xval;
y=y*2+yval;
G[x].push_back(y);
}
bool solve()
{
for(int i=0;i<2*n;i+=2)
if(!mark[i] && !mark[i+1])
{
c=0;
if(!dfs(i))
{
while(c>0) mark[S[--c]]=false;
if(!dfs(i+1)) return false;
}
}
return true;
}
}TS;
bool ok(int mid)
{
TS.init(n);
for(int i=0;i<n/2;i++)
{
TS.add_clause(a[i],0,b[i],1);
TS.add_clause(b[i],0,a[i],1);
}
for(int i=0;i<mid;i++) //注意这里是i<mid,而不是i<m
{
TS.add_clause(c[i],1,d[i],0);
TS.add_clause(d[i],1,c[i],0);
}
return TS.solve();
}
int main()
{
while(scanf("%d%d",&n,&m)==2&&n)
{
n*=2;
for(int i=0;i<n/2;i++) scanf("%d%d",&a[i],&b[i]);
for(int i=0;i<m;i++) scanf("%d%d",&c[i],&d[i]);
int L=0, R=m;
while(R>L)
{
int mid = L+(R-L+1)/2;
if(ok(mid)) L=mid;
else R=mid-1;
}
printf("%d\n",L);
}
return 0;
}