题目链接:http://poj.org/problem?id=3648
题意:有n对夫妻坐成两排,其中第0对为新娘新郎。有m对奸情,新娘不愿意看到对面排存在奸情,问是否有合理方案,有输出一组解,否则输出"bad luck";
思路:
每对夫妻要么和新郎一排,要么和新娘一排所以容易想到用2-sat.
我们将丈夫编号为1~n,其对应的妻子编号+n(下面用i表示丈夫,i'表示妻子).
每对奸情则即是矛盾,则与其不矛盾的点连边,比如a与b矛盾,则连a->b',b->a',其意思是选a 必须连b',不能连b。
这里用一个数组(kx[])表示该点是否可选,或没被选。 初始值为0,表示未被选,1表示可选及已选,-1表示不可选。
每次选择了一个点后,就暴力搜接下来必须选择的点,然后把他们都选择(相连边的点遵循要选一起选的原则),其伴侣都标记为不选择。如果不合法就换一种方式搜。
连边时要加一条新娘到新郎的边,因为新郎也可能有奸情,使其矛盾。
过程例如以下:a与新郎有奸情,矛盾,在开始建图时会使a->新娘,当如果爆搜选了a,则下面选新娘,标记新郎不可选,再通过建的这条边,发现爆搜a不可行,这样就避免了新郎与奸情坐一侧,这点很重要,而且我想了很久很久。。。。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=1e5+10; struct node{ int u,v,next; }e[maxn]; int head[maxn],kx[maxn],n,m,top,cnt,st[maxn]; //kx数组判断是否可选,初始值为0, 1为可选且已选,-1为不可选。 int cp(int x){ return x<=n?x+n:x-n; }//返回cp编号 void add(int u,int v){e[cnt].u=u,e[cnt].v=v,e[cnt].next=head[u],head[u]=cnt++;} bool dfs(int x)//爆搜,判断编号x是否可选 { if(kx[x]==1) return true;//可选,且已被选。 if(kx[x]==-1) return false;//不可选 kx[x]=1,kx[cp(x)]=-1,st[++top]=x;//没被选,但是可被选,所以选入,其cp标为不可选。 for(int i=head[x];i!=-1;i=e[i].next) { if(!dfs(e[i].v))//判断相连的点是否可选,要选一起选。 return false; } return true; } void solve() { for(int i=1;i<=n;i++) { if(kx[i])//由0~i-1 连边判断已被选,不用进行下面操作 continue; top=0; if(!dfs(i))//kx[i]==0,没被选 ,且不可选 { while(top)//返回由i推到已选点,重新变成没选状态。 { kx[st[top]]=kx[cp(st[top])]=0; top--; } if(!dfs(i+n))//i不可选,且i+n也不可选,说明寻找不到答案。 { cout<<"bad luck"<<endl; return ; } } } for(int i=1;i<n;i++)//可以找到解 { if(kx[i]==-1)//当i不可选,则表示新娘不可见,放到新娘一边 cout<<i<<"h "; else//否则i+n不可选,所以i的妻子放在新娘一边 cout<<i<<"w "; } cout<<endl; } int main() { std::ios::sync_with_stdio(false); int x,y; char x1,y1; while(cin>>n>>m) { if(!n&&!m) break; memset(kx,0,sizeof(kx)); memset(head,-1,sizeof(head)); cnt=0; for(int i=0;i<m;i++) { cin>>x>>x1>>y>>y1; if(!x) x=n; if(!y) y=n; if(x1=='h'&&y1=='h') add(x,y+n),add(y,x+n); else if(x1=='h'&&y1=='w') add(x,y),add(y+n,x+n); else if(x1=='w'&&y1=='h') add(x+n,y+n),add(y,x); else if(x1=='w'&&y1=='w') add(x+n,y),add(y+n,x); } add(2*n,n);//加一条新娘到新郎的边,因为新郎可能有奸情 solve(); } return 0; }
上面采用爆搜的方法,时间复杂度为O(n*m),有点高,但如果求字典序最小方案只能爆搜。
而如果采用拓扑排序时间复杂度先对较低,为O(m),但只能求出一组随机解,这题可以。
怎么个思路呢?
在连通分支中的点,如果选了一个点,那么这个连通分支的点都要选,证明就不证了。
我们将先连的连通分支反向连接。再拓扑排序继续用kx[]判断是否可选即可。
为什么反向连边呢?
"选择”标记是没有进行传递的,也就是说正向边是没有利用到的,传递的都是“不选择”标记,也就是说走的都是反边
具体可以看大佬的博客:原来2-SAT是这么一回事
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=1e5+10; struct node{ int u,v,next; }e[maxn],e2[maxn]; int h[maxn],h2[maxn]; int st[maxn],visit[maxn],dfn[maxn],low[maxn],belong[maxn]; int kx[maxn],opp[maxn],in[maxn],n,m,cnt,cnt2,num,top,tot; //opp数组存为对立点,相当于连通分量矛盾的点(不可同时选)。 void init() { memset(h,-1,sizeof(h)); memset(h2,-1,sizeof(h2)); memset(visit,0,sizeof(visit)); memset(dfn,0,sizeof(dfn)); memset(belong,0,sizeof(belong)); memset(kx,0,sizeof(kx)); memset(opp,0,sizeof(opp)); memset(in,0,sizeof(in)); cnt=cnt2=top=num=tot=0; } void add(int u,int v) { e[cnt].u=u; e[cnt].v=v; e[cnt].next=h[u]; h[u]=cnt++; } void add2(int u,int v) { e2[cnt2].u=u; e2[cnt2].v=v; e2[cnt2].next=h2[u]; h2[u]=cnt2++; } void tarjan(int u)//tarjan缩点 { low[u]=dfn[u]=++tot; visit[u]=1; st[++top]=u; for(int i=h[u];i!=-1;i=e[i].next) { int v=e[i].v; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } if(visit[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { int t; num++; do{ t=st[top--]; visit[t]=0; belong[t]=num; }while(t!=u); } } void topsort()//拓扑排序 { top=0; for(int i=1;i<=num;i++) { if(!in[i]) st[++top]=i; } while(top) { int u=st[top--]; if(!kx[u]) { kx[u]=1; kx[opp[u]]=-1; } for(int i=h2[u];i!=-1;i=e2[i].next) { int v=e2[i].v; in[v]--; if(!in[v]) st[++top]=v; } } } void solve() { for(int i=1;i<=2*n;i++) { if(!dfn[i]) tarjan(i); } for(int i=1;i<=n;i++) { if(belong[i]==belong[i+n])//不成立,矛盾点在同一连通分量里 { cout<<"bad luck"<<endl; return ; } opp[belong[i]]=belong[i+n]; opp[belong[i+n]]=belong[i]; } for(int i=1;i<=2*n;i++)//建连通分量图 { for(int j=h[i];j!=-1;j=e[j].next) { int v=e[j].v; if(belong[v]!=belong[i])//两个点不同才相连 { add2(belong[v],belong[i]);//反向连边 in[belong[i]]++; } } } topsort(); for(int i=1;i<n;i++)//解的方式和上面一致。 { if(kx[belong[i]]==-1) cout<<i<<"h "; else cout<<i<<"w "; } cout<<endl; } int main() { std::ios::sync_with_stdio(false); int x,y; char x1,y1; while(cin>>n>>m) { if(!n&&!m) break; init(); for(int i=0;i<m;i++) { cin>>x>>x1>>y>>y1; if(!x) x=n; if(!y) y=n; if(x1=='h'&&y1=='h') add(x,y+n),add(y,x+n); if(x1=='h'&&y1=='w') add(x,y),add(y+n,x+n); if(x1=='w'&&y1=='h') add(x+n,y+n),add(y,x); if(x1=='w'&&y1=='w') add(x+n,y),add(y+n,x); } add(2*n,n); solve(); } return 0; }