UVA11294 Wedding 题解
前排提示:本题需要用到知识点 2-SAT 以及强联通分量,模板传送门 P4782 【模板】2-SAT。
题目大意:
有至多 \(30\) 对夫妻将会参加一个婚宴。他们将会坐在一个长桌子的两边。
新郎新娘坐在彼此相对的一端并且新娘带着一个头饰使得她看不到和她坐在同一边的人。夫妻坐在同一边是被视为“晦气”。并且这些人中还有人是通奸关系,并且新娘同时看到任何一对中的两个人也被视为“晦气”。你的工作是去安排人们坐在桌子两边从而避免任何“晦气”。
题目分析:
本题比较容易看出来是 2-SAT 的套路,考虑将 \(n\) 对夫妻拆成 \(2n\) 点,第 \(i\) 对夫妻在代码中记作为 \(i\) 和 \(i+n\), \(x\) 表示妻子与新郎同侧,\(\lnot x\) 表示丈夫与新郎同侧。
对于一对关系 \((x,y)\) 表示 \(x\) 和 \(y\) 不能坐在桌子的同一边,故 \(x\) 向 \(\lnot y\) 连边,\(y\) 向 \(\lnot x\) 连边。
最后考虑跑 Tarjan 染色,注意输出的时候需要将人物相反(即新郎同侧的是妻子,新娘同侧的是丈夫)。
注意事项:
- 本题多测,记得清空数据;
- 编号从 \(0\) 开始,可以将所有编号 \(+1\),最后输出的时候记得 \(-1\),同理如果不改变编号用链式前向星存边的需要注意将 \(head[]\) 数组初始化为 \(-1\);
- 新娘和新郎别忘了也要连边,若将所有的编号 \(+1\),则别忘了对应的连边
add(1,1+n);
;
Code:
//i 表示第 i 对的丈夫和新娘坐 i+n 表示第 i 对的妻子和新娘坐
#include<bits/stdc++.h>
using namespace std;
const int N=65;
int n,m;
int dfn[N],low[N],bel[N],cnt,tc;
bool vis[N],flag;
vector<int> g[N];
stack <int> stk;
void init()//多测初始化清空
{
cnt=tc=flag=0;
for(int i=1;i<N;i++) g[i].clear();//别忘了边也要清空
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(bel,0,sizeof(bel));
}
void Tarjan(int u)//Tarjan 模板
{
low[u]=dfn[u]=++tc;
vis[u]=1;
stk.push(u);
for(auto v:g[u])
{
if(!dfn[v]) Tarjan(v),low[u]=min(low[u],low[v]);
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
++cnt;
do
{
bel[u]=cnt;//染色,记录答案
u=stk.top(),stk.pop();
vis[u]=0;
}while(low[u]^dfn[u]);
}
}
int main()
{
while(scanf("%d%d",&n,&m)&&(n+m))//多测注意结束条件为 n=0,m=0
{
init();//多测要清空
g[1].push_back(1+n);//新娘向新郎连边
while(m--)
{
int i,a,j,b;
char x;
scanf("%d%c",&i,&x),++i;//编号 +1
a=(x=='w'?0:1);
scanf("%d%c",&j,&x),++j;
b=(x=='w'?0:1);
g[i+n*(a&1)].push_back(j+n*(b^1));//省去了 if 判断过程
g[j+n*(b&1)].push_back(i+n*(a^1));
}
for(int i=1;i<=n<<1;i++)
if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++)
if(bel[i]==bel[i+n])
{//若同时存在 x 与 ¬x 染上同样的颜色(处于同一环上),即坐在桌子的同一侧,那么输出 bad luck。
flag=1;
break;
}
if(flag)
{
puts("bad luck");//接上
continue;
}
for(int i=2;i<=n;i++)
printf("%d%c ",i-1,(bel[i]<bel[i+n]?'h':'w'));//注意输出
puts(" ");
}
return 0;
}