题目链接:http://poj.org/problem?id=3648
本文作者:kuangbin
(转载请注明出处,博客:www.cnblogs.com/kuangbin)
【题目大意】很多对夫妇参加一对新人的婚礼。分别做在长桌子的两侧。新郎、新娘分别坐两侧,新娘只能看到她对面的人。新娘不想看到她对面有夫妇。
而且有一些人是有通奸关系的(男的和男的有,女的和男的、女的和女的都可能有,而且新郎也可能和别人有通奸关系),新娘不想看到有通奸关系一对人。
也就是有通奸关系的不能一起坐在新娘对面。
输入是:_n对夫妇(包括新郎新娘在女的,编号为0-(n-1),新郎、新娘那一对的编号为0),_m对通奸关系。
接下来_m行有通奸关系的。h表示男的,w表是女的,3w 5h即表示第三对夫妇的女的和第五对夫妇的男的有不寻常关系。
【题目类别】2-SAT
【题目分析】题目就是需要选出包括新郎在内的一半人坐在新娘对面去,选出来的人不能有夫妇,不能有通奸关系的一对人。
编号分别为0-2*_n,奇数是男的,偶数是女的。自然0是新娘,1是新郎了。
为了一定选到1,可以加一条边0->1,这样选了新娘就必定会选当新郎,从而判断错误。这样如果有符合题意的_n个人选出来,
则选出来的肯定包括新郎了。
注意的是输出的时候是输出和新娘坐同一边的人。即反过来输出就可以了;
代码:
/*
POJ3648
求字典序最小的解
*/
#include<iostream>
#include<stdio.h>
using namespace std;
const int MAXN=20000;
const int MAXM=100000;//这个必须开大一点
struct Node
{
int a,b,pre,next;
}E[MAXM],E2[MAXM];//原图和逆图
int _n,n,m,V[MAXN],ST[MAXN][2],Q[MAXN],Q2[MAXN],vst[MAXN];
bool res_ex;
void init_d()//初始化
{
for(int i=0;i<n;i++)//相当于建出双重邻接表的头结点
E[i].a=E[i].pre=E[i].next=E2[i].a=E2[i].pre=E2[i].next=i;
m=n;//m是建造双重邻接表时结点的编号
}
void add_edge(int a,int b)//加入边(a,b),需要在原图和逆图中添加边
{//实际上建造出的是循环状的图
E[m].a=a;E[m].b=b;E[m].pre=E[a].pre;E[m].next=a;E[a].pre=m;E[E[m].pre].next=m;
E2[m].a=b;E2[m].b=a;E2[m].pre=E2[b].pre;E2[m].next=b;E2[b].pre=m;E2[E2[m].pre].next=m++;
}
void solve()
{
for(int i=0;i<n;i++)
{
V[i]=0;
vst[i]=0;
}
res_ex=1;
int i,i1,i2,j,k,len,front,rear,front2,rear2;
bool ff;
for(int _i=0;_i<_n;_i++)
{
if(V[_i<<1]==1||V[(_i<<1)+1]==1) continue;//找一对未被确定的点
i=_i<<1;len=0;
if(!V[i])
{
ST[len][0]=i;ST[len++][1]=1;
if(!V[i ^ 1])
{
ST[len][0]=i^1;
ST[len++][1]=2;
}
Q[front=rear=0]=i;
vst[i]=i1=n+i;
Q2[front2=rear2=0]=i^1;
vst[i^1]=i2=(n<<1)+i;
//i1,i2为标志量,这样设置标志量使得每次都不一样,省去了初始化
ff=1;
for(;front<=rear;front++)
{
j=Q[front];
for(int p=E[j].next;p!=j;p=E[p].next)
{
k=E[p].b;
if(V[k]==2||vst[k]==i2||V[k^1]==1||vst[k^1]==i1)
{ff=0;break;}
if(vst[k]!=i1)
{
Q[++rear]=k;vst[k]=i1;
if(!V[k])
{
ST[len][0]=k;
ST[len++][1]=1;
}
}
if(vst[k^1]!=i2)
{
Q2[++rear2]=k^1;vst[k^1]=i2;
if(!V[k])
{
ST[len][0]=k^1;
ST[len++][1]=2;
}
}
}
if(!ff) break;
}
if(ff)
{
for(;front2<=rear2;front2++)
{
j=Q2[front2];
for(int p=E2[j].next;p!=j;p=E2[p].next)
{
k=E2[p].b;
if(V[k]==1||vst[k]==i1)
{
ff=0;
break;
}
if(vst[k]!=i2)
{
vst[k]=i2;Q2[++rear]=k;
if(!V[k])
{
ST[len][0]=k;
ST[len++][1]=2;
}
}
}
if(!ff) break;
}
if(ff)
{
for(int j=0;j<len;j++) V[ST[j][0]]=ST[j][1];
continue;
}
}
}
i=(_i<<1)+1;len=0;
if(!V[i])
{
ST[len][0]=i;ST[len++][1]=1;
if(!V[i ^ 1])
{
ST[len][0]=i^1;
ST[len++][1]=2;
}
Q[front=rear=0]=i;
vst[i]=i1=n+i;
Q2[front2=rear2=0]=i^1;
vst[i^1]=i2=(n<<1)+i;
ff=1;
for(;front<=rear;front++)
{
j=Q[front];
for(int p=E[j].next;p!=j;p=E[p].next)
{
k=E[p].b;
if(V[k]==2||vst[k]==i2||V[k^1]==1||vst[k^1]==i1)
{ff=0;break;}
if(vst[k]!=i1)
{
Q[++rear]=k;vst[k]=i1;
if(!V[k])
{
ST[len][0]=k;
ST[len++][1]=1;
}
}
if(vst[k^1]!=i2)
{
Q2[++rear2]=k^1;vst[k^1]=i2;
if(!V[k])
{
ST[len][0]=k^1;
ST[len++][1]=2;
}
}
}
if(!ff) break;
}
if(ff)
{
for(;front2<=rear2;front2++)
{
j=Q2[front2];
for(int p=E2[j].next;p!=j;p=E2[p].next)
{
k=E2[p].b;
if(V[k]==1||vst[k]==i1)
{
ff=0;
break;
}
if(vst[k]!=i2)
{
vst[k]=i2;Q2[++rear]=k;
if(!V[k])
{
ST[len][0]=k;
ST[len++][1]=2;
}
}
}
if(!ff) break;
}
if(ff)
{
for(int j=0;j<len;j++) V[ST[j][0]]=ST[j][1];
continue;
}
}
}
if(V[_i<<1]+V[(_i<<1)+1]!=3){res_ex=0;break;}
}
}
int main()
{
int _m,a,b;
char ch1,ch2;
while(scanf("%d%d",&_n,&_m)!=EOF)//_n是点的对数,_m是冲突的点个数
{
if(_n==0&&_m==0)break;
n=_n<<1;
init_d();
for(int i=0;i<_m;i++)
{
scanf("%d%c%d%c",&a,&ch1,&b,&ch2);
if(ch1=='w') a=2*a;
else a=2*a+1;
if(ch2=='w') b=2*b;
else b=2*b+1;
if(a!=(b^1))
{
add_edge(a,b^1);//选a,必选b^1,
add_edge(b,a^1);//选b,必选a^1,
}
}
add_edge(0,1);//加一条新娘到新郎的边。
//表示选了新娘必选新郎,这样如果选了新娘就会判断无解。
//这样选出来的组合必定是有新郎的,即和新郎坐在同一侧的
solve();
bool first=false;
if(res_ex)
{
for(int i=0;i<n;i++)
{
if(V[i]==1&&i!=1)
{
if(first)printf(" ");
else first=true;
printf("%d%c",i/2,i%2==0?'h':'w');//选取的是和新郎同一侧的,输出和新娘同一侧的
//所以输出的时候h和w换一下
}
}
printf("\n");
}
else printf("bad luck\n");
}
return 0;
}