poj2723 Get Luffy Out
题意:给出 n 对钥匙,每对只能挑一把使用,每把只能用一次,当一对钥匙中的一把被使用后,另一把也就不能再用了;然后给出 m 道门,每个门都有两把钥匙可以打开,问最多能开几道门(按给出的顺序开)。
矛盾关系:
1:n 对钥匙中,A 和 B 只能选择一把,用点 A 表示选择钥匙 A ,用 A’ 表示不选择(同理用点 B 和 B‘ 表示钥匙 B 的选择关系),建边(A -> B‘)表示用钥匙 A 就不能用钥匙 B ;还有(B -> A' )表示用 B 就不能用 A。
2:m 道门,每对门都有两把钥匙可以开(假设是 C 和 D ),可能的选择是(用 C 不用 D)或者(用 D 不用 C),根据这个关系建边(D' -> C),(C’ -> D)。
(PS:上面第二步中的(用 C 不用 D)和(用 D 不用 C)如果建成边(C -> D'),(D -> C')就会错,这样建边并不违反 2-SAT 问题的建边规则啊。不过最后发现,如果这样建边的话,图中的所有边都是由 X 指向 X‘ 的,那么对其求强连通是没有意义的。具体什么原因,那位神牛路过时,帮忙解释下啊,不胜感激)
建完图,可以考虑2分枚举答案:
#include<cstdio> #include<cstring> #include<stack> #include<climits> using namespace std; const int N = 4100; struct Edge{ int s,e,next; }edge[3*N]; int n,m,e_num,vis_num,cnt,head[N],instack[N],low[N],tim[N],belong[N]; void AddEdge(int a,int b){ edge[e_num].s=a; edge[e_num].e=b; edge[e_num].next=head[a]; head[a]=e_num++; } stack <int>st; void tarjan(int x){ int i; tim[x]=low[x]=++vis_num; instack[x]=1; st.push(x); for(i=head[x];i!=-1;i=edge[i].next){ int u=edge[i].e; if(tim[u]==-1){ tarjan(u); if(low[x]>low[u])low[x]=low[u]; } else if(instack[u] && low[x]>tim[u])low[x]=tim[u]; } if(low[x]==tim[x]){ cnt++; do{ i=st.top(); st.pop(); instack[i]=0; belong[i]=cnt; }while(i!=x); } } void init(){ vis_num=cnt=0; memset(instack,0,sizeof(instack)); memset(belong,-1,sizeof(belong)); memset(tim,-1,sizeof(tim)); memset(low,0,sizeof(low)); } int main() { int i,left,right,mid,ans; int a[2][N],x[N],y[N]; while(scanf("%d%d",&n,&m),n+m) { memset(x,-1,sizeof(x)); memset(y,-1,sizeof(y)); for(i=0;i<n;i++)//2*i 和 2*i+1 分别表示用 i 钥匙和不用 i 钥匙 scanf("%d%d",&x[i],&y[i]); for(i=0;i<m;i++) scanf("%d%d",&a[0][i],&a[1][i]); left=0; right=m; while(left<=right){ e_num=0; memset(head,-1,sizeof(head)); for(i=0;i<n;i++){//2*i 和 2*i+1 分别表示用 i 钥匙和不用 i 钥匙 AddEdge(2*x[i],2*y[i]+1); AddEdge(2*y[i],2*x[i]+1); } mid=(left+right)/2; for(i=0;i<mid;i++){ AddEdge(2*a[0][i]+1,2*a[1][i]); AddEdge(2*a[1][i]+1,2*a[0][i]); } init(); for(i=0;i<4*n;i++){ if(tim[i]==-1)tarjan(i); } int flag=1; for(i=0;i<2*n;i++){ if(belong[2*i]==belong[2*i+1]){ flag=0;break; } } if(flag){ ans=mid; left=mid+1; } else right=mid-1; } printf("%d\n",ans); } return 0; }