题意:有2n把钥匙,分成2组,给你每组的钥匙信息,并且每组的钥匙只能用一个。m个门,每个门有2个锁,只要打开一个锁这个门就开了。(顺序遇见m个门)问你最多能够打开多少个门。
建边方式:(需要建2类边,对于每组钥匙(A,B),增加点对(A',B'))
1:n 对钥匙中,A 和 B 只能选择一把,用点A 默示选择钥匙 A ,用 A’ 默示不选择(同理用点B 和B‘ 默示钥匙 B 的选择关系),建边(A -> B‘)默示用钥匙 A 就不用钥匙 B ;还有(B -> A')默示用 B 就不用A。
2:m 道门,每对门都有两把钥匙可以开,假设是A和B ,可能的选择是(用A不消B)或者(用B 不消A),按照这个关系建边(B' -> A),(A’ -> B)
建完边后用2分方法即可求出最多能达到第几扇门
2-sat的题目建边是关键,而2-sat建边的原理就是找2个不相容点再进行建边
为什么对门上钥匙建边的时候只能是(A'->B),而不能是(A->B')?
因为建边的时候要求是2个不相容的点对之间建边,而(A,B)不是不相容点对,(A',B')才是不相容点对,不相容点对的意思就是说2个条件不能同时存在,而A,B是可以同时存在的,用A,B同时打开了门上的2把锁,门还是照样能开,相容。而(A',B')表示既不用A钥匙也不用B钥匙,这种情况下门是不能打开的,所以这2个点不能同时存在,同时存在就打不开门,所以为不相容点对,而在建边的时候,每建一条边,如(X->Y)表示选择X后必须选择Y,所以(A'->B)则表示不用A钥匙则必须用B钥匙来开,(B'->A)表示不用B钥匙则必须用A钥匙来。
关于建边的一点点总结:
(1)必须要在不相容点对之间建边,如上面的(A',B'),这2个条件同时存在问题就不能解决,就像这题中的如果2把钥匙都不用,则门肯定打不开
(2)建一条边看下,前面是不是一定能推到后面,像本题中的(A'->B)不用A钥匙则必须要用B钥匙,别无选择,不然门就打不开,而(A->B')这种建边则表示用了A钥匙则一定不能用B钥匙,我可以A,B钥匙同时用,如(A->B),前面不一定能推到后面,所以不可行
源代码:
# include<cstdio>
# include<cstring>
# include<vector>
# include<stack>
using namespace std;
# define N 10000
int dfn[N],low[N],instack[N],belong[N],now,cnt,n,m;
stack<int> sta;
vector<int> vec[N];
struct node
{
int x;
int y;
}p[N],q[N];
void tarjan(int v)
{
int i,u,size;
dfn[v]=low[v]=++now;
sta.push(v);
instack[v]=1;
size=vec[v].size();
for(i=0;i<size;i++)
{
u=vec[v][i];
if(!dfn[u])
{
tarjan(u);
low[v]=min(low[v],low[u]);
}
else if(instack[u])
low[v]=min(low[v],dfn[u]);
}
if(dfn[v]==low[v])
{
cnt++;
while(!sta.empty())
{
u=sta.top();
sta.pop();
instack[u]=0;
belong[u]=cnt;
if(u==v)
break;
}
}
}
int solve(int mid)
{
int i;
for(i=0;i<4*n;i++)
vec[i].clear();
now=cnt=0;
memset(instack,0,sizeof(instack));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(belong,0,sizeof(belong));
for(i=0;i<n;i++)
{
vec[2*p[i].x].push_back(2*p[i].y+1);
vec[2*p[i].y].push_back(2*p[i].x+1);
}
for(i=0;i<mid;i++)
{
vec[2*q[i].x+1].push_back(2*q[i].y);
vec[2*q[i].y+1].push_back(2*q[i].x);
}
for(i=0;i<4*n;i++)
if(!dfn[i])
tarjan(i);
for(i=0;i<2*n;i+=2)
if(belong[i]==belong[i+1])
return 0;
return 1;
}
int ans()
{
int l=0,r=m,mid,ans;
while(l<=r)
{
mid=(l+r)>>1;
if(solve(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
return ans;
}
int main()
{
int i;
while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
{
for(i=0;i<n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
for(i=0;i<m;i++)
scanf("%d%d",&q[i].x,&q[i].y);
printf("%d\n",ans());
}
return 0;
}
最后,我们再增加点步骤看看经过经过前面几道门分别需要哪些钥匙,即看看最后的结果
建立反向图-->拓扑排序(着色)-->输出结果
# include<cstdio>
# include<cstring>
# include<vector>
# include<stack>
# include<queue>
using namespace std;
# define N 10000
int dfn[N],low[N],instack[N],belong[N],color[N],in[N],opp[N],now,cnt,n,m;
stack<int> sta;
vector<int> vec[N],dag[N];
queue<int> que;
struct node
{
int x;
int y;
}p[N],q[N];
void tarjan(int v)
{
int i,u,size;
dfn[v]=low[v]=++now;
sta.push(v);
instack[v]=1;
size=vec[v].size();
for(i=0;i<size;i++)
{
u=vec[v][i];
if(!dfn[u])
{
tarjan(u);
low[v]=min(low[v],low[u]);
}
else if(instack[u])
low[v]=min(low[v],dfn[u]);
}
if(dfn[v]==low[v])
{
cnt++;
while(!sta.empty())
{
u=sta.top();
sta.pop();
instack[u]=0;
belong[u]=cnt;
if(u==v)
break;
}
}
}
void buliddag() //建立反向图
{
int i,v,u;
for(v=0;v<4*n;v++)
{
for(i=0;i<vec[v].size();i++)
{
u=vec[v][i];
if(belong[u]!=belong[v])
{
dag[belong[u]].push_back(belong[v]);
in[belong[v]]++;
}
}
}
}
void topsort() //拓扑排序,着色
{
int i,v,u;
for(i=1;i<=cnt;i++)
if(in[i]==0)
que.push(i);
while(!que.empty())
{
u=que.front();
que.pop();
if(!color[u])
{
color[u]=1;
color[opp[u]]=2;
}
for(i=0;i<dag[u].size();i++)
{
v=dag[u][i];
in[v]--;
if(in[v]==0)
que.push(v);
}
}
}
int solve(int mid)
{
int i;
for(i=0;i<4*n;i++)
{
vec[i].clear();
dag[i].clear();
}
now=cnt=0;
memset(instack,0,sizeof(instack));
memset(color,0,sizeof(color));
memset(in,0,sizeof(in));
memset(opp,0,sizeof(opp));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(belong,0,sizeof(belong));
for(i=0;i<n;i++)
{
vec[2*p[i].x].push_back(2*p[i].y+1);
vec[2*p[i].y].push_back(2*p[i].x+1);
}
for(i=0;i<mid;i++)
{
vec[2*q[i].x+1].push_back(2*q[i].y);
vec[2*q[i].y+1].push_back(2*q[i].x);
}
for(i=0;i<4*n;i++)
if(!dfn[i])
tarjan(i);
for(i=0;i<4*n;i+=2)
{
if(belong[i]==belong[i+1])
{
printf("The %d door can't go on!\n",mid);
return 0;
}
else
{
opp[belong[i]]=belong[i+1];
opp[belong[i+1]]=belong[i];
}
}
buliddag();
topsort();
printf("Pass the %d doors,you should use: ",mid);
for(i=0;i<4*n;i+=2)
{
if(color[belong[i]]==1)
printf("key(%d) ",i/2);
}
printf("\n");
return 1;
}
int ans()
{
int l=0,r=m,mid,ans;
while(l<=r)
{
mid=(l+r)>>1;
if(solve(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
return ans;
}
int main()
{
int i;
while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
{
for(i=0;i<n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
for(i=0;i<m;i++)
scanf("%d%d",&q[i].x,&q[i].y);
for(i=1;i<=m;i++)
if(!solve(i))
break;
// printf("%d\n",ans());
}
return 0;
}