題目鏈接: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;
}
文章來源:
http://www.cnblogs.com/kuangbin/archive/2011/08/22/2149667.html