[JSOI2010] 满汉全席
题目描述
为了招收新进的厨师进入世界满汉全席协会,将于近日举办满汉全席大赛,协会派遣许多会员当作评审员,为的就是要在参赛的厨师之中,找到满汉界的明日之星。
大会的规则如下:每位参赛的选手可以得到 n n n 种材料,选手可以自由选择用满式或是汉式料理将材料当成菜肴。
只要参赛者能在这两种材料的做法中,其中一个符合评审的喜好即可通过该评审的审查。如材料有猪肉,羊肉和牛肉时,有四位评审员的喜好如下表:
评审一 评审二 评审三 评审四
满式牛肉 满式猪肉 汉式牛肉 汉式牛肉
汉式猪肉 满式羊肉 汉式猪肉 满式羊肉
参赛者做出汉式猪肉,满式羊肉和满式牛肉料理,就可以满足所有评审的要求。
但大会后来发现,在这样的制度下如果材料选择跟派出的评审员没有特别安排好的话,所有的参赛者最多只能通过部分评审员的审查而不是全部,所以可能会发生没有人通过考核的情形。
如有四个评审员喜好如下表时,则不论参赛者采取什么样的做法,都不可能通过所有评审的考核:
评审一 评审二 评审三 评审四
满式羊肉 满式猪肉 汉式羊肉 汉式羊肉
汉式猪肉 满式羊肉 汉式猪肉 满式猪肉
所以大会希望有人能写一个程序来判断,所选出的 m m m 位评审,会不会发生没有人能通过考核的窘境,以便协会组织合适的评审团。
输入格式
第一行包含一个数字 K K K( 1 ≤ K ≤ 50 1\le K \le 50 1≤K≤50),代表测试文件包含了 K K K 组数据。
每一组测试数据的第一行包含两个数字 n n n 跟 m m m( n ≤ 100 n≤100 n≤100, m ≤ 1000 m≤1000 m≤1000),代表有 n n n 种材料, m m m 位评审员。
为方便起见,舍弃做法的中文名称而给予编号,编号分别从 1 1 1 到 n n n。
接下来的 m m m 行,每行都代表对应的评审员所拥有的两个喜好,每个喜好由一个英文字母跟一个数字代表,如 m 1 m1 m1 代表这个评审喜欢第 1 1 1 个材料透过满式料理做出来的菜,而 h 2 h2 h2 代表这个评审员喜欢第 2 2 2 个材料透过汉式料理做出来的菜。
输出格式
每组测试数据输出一行,如果不会发生没有人能通过考核的窘境,输出 GOOD
;否则输出 BAD
(均为大写字母)。
样例输入
2
3 4
m3 h1
m1 m2
h1 h3
h3 m2
2 4
h1 m2
m2 m1
h1 h2
m1 h2
样例输出
GOOD
BAD
分析
2-SAT
SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 k>2 时该问题为 NP 完全的。所以我们只研究 k=2 的情况。
2-SAT可以通过约束条件来建立bool方程,也就是说不选什么能够推出一定要选什么,然后根据“推出关系”,链接有向边,转化为图论问题。
举个栗子:
2-SAT常用解决方法
Tarjan SCC 缩点
算法考究在建图这点,我们举个例子来讲:
假设有 {a1,a2} 和 {b1,b2} 两对,已知 a1 和 b2 间有矛盾,于是为了方案自洽,由于两者中必须选一个,所以我们就要拉两条有向边 (a1,b1) 和 (b2,a2) 表示选了 a1 则必须选 b1,选了 b2 则必须选 a2 才能够自洽。
然后通过这样子建边我们跑一遍 Tarjan SCC 判断是否有一个集合中的两个元素在同一个 SCC 中,若有则输出不可能,否则输出方案。构造方案只需要把几个不矛盾的 SCC 拼起来就好了。
输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果变量 x 的拓扑序在 ¬ x \neg x ¬x 之后,那么取 x 值为真。应用到 Tarjan 算法的缩点,即 x 所在 SCC 编号在 ¬ x \neg x ¬x 之前时,取 x 为真。因为 Tarjan 算法求强连通分量时使用了栈,所以 Tarjan 求得的 SCC 编号相当于反拓扑序。
显然地,时间复杂度为 O ( n + m ) O(n+m) O(n+m)。
第二种处理方法:爆搜
就是沿着图上一条路径,如果一个点被选择了,那么这条路径以后的点都将被选择,那么,出现不可行的情况就是,存在一个集合中两者都被选择了。
这种一般对于复杂度要求不高,但是输出的构造方案一般有要求,比如字典序最小等。
模板【洛谷P4782】
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;
struct node
{
int to,next;
}e[4000010];
int n,m;
int tot,hd[4000010];
int dfn[2000010],col[2000010],low[2000010],timi,cnt;
void add(int x,int y)
{
e[++tot]=(node){y,hd[x]};
hd[x]=tot;
}
stack<int> st;
void tarjan(int x)
{
dfn[x]=low[x]=++timi;
st.push(x);
for(int i=hd[x];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(!col[v]) low[x]=min(low[x],low[v]);
}
if(dfn[x]==low[x])
{
col[x]=++cnt;
while(st.top()!=x)
{
col[st.top()]=cnt;
st.pop();
}
st.pop();
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,a,y,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
if(a==1&&b==1)
{
add(x+n,y);
add(y+n,x);
}
else if(a==1&&b==0)
{
add(x+n,y+n);
add(y,x);
}
else if(a==0&&b==1)
{
add(x,y);
add(y+n,x+n);
}
else if(a==0&&b==0)
{
add(x,y+n);
add(y,x+n);
}
}
for(int i=1;i<=n*2;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
if(col[i]==col[i+n])
{
cout<<"IMPOSSIBLE";
return 0;
}
}
cout<<"POSSIBLE"<<endl;
for(int i=1;i<=n;i++)
{
if(col[i]<col[i+n]) cout<<1<<' ';//这里是反拓扑序
else cout<<0<<' ';
}
return 0;
}
回到这道题
明显有2-SAT的模型,于是我们根据满和汉的做法来建图(注意字符串的处理),然后tarjan就行,跟上一题神似,注意反拓扑序。
关键在于识别模型,不要误以为是二分图!!
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;
struct node
{
int to,next;
}e[4010];
int t,n,m,ff;
int dfn[1010],low[1010],col[1010],timi,cnt;
int hd[4010],tot;
void add(int x,int y)
{
e[++tot]=(node){y,hd[x]};
hd[x]=tot;
}
stack<int> st;
void tarjan(int x)
{
dfn[x]=low[x]=++timi;
st.push(x);
for(int i=hd[x];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(!col[v]) low[x]=min(low[x],low[v]);
}
if(dfn[x]==low[x])
{
col[x]=++cnt;
while(st.top()!=x)
{
col[st.top()]=cnt;
st.pop();
}
st.pop();
}
}
int main()
{
cin>>t;
while(t--)
{
cin>>n>>m;
char c1[10],c2[10];
for(int i=1;i<=m;i++)
{
scanf("%s%s",&c1,&c2);
int a=0,b=0,k=1;
while(c1[k]>='0'&&c1[k]<='9') a=a*10+c1[k++]-48;
k=1;
while(c2[k]>='0'&&c2[k]<='9') b=b*10+c2[k++]-48;
if(c1[0]=='m'&&c2[0]=='m')
{
add(a,b+n);
add(b,a+n);
}
if(c1[0]=='m'&&c2[0]=='h')
{
add(a,b);
add(b+n,a+n);
}
if(c1[0]=='h'&&c2[0]=='m')
{
add(a+n,b+n);
add(b,a);
}
if(c1[0]=='h'&&c2[0]=='h')
{
add(a+n,b);
add(b+n,a);
}
}
for(int i=1;i<=2*n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
if(col[i]==col[i+n])
{
cout<<"BAD"<<endl;
ff=1;
break;
}
}
if(!ff) cout<<"GOOD"<<endl;
memset(dfn,0,sizeof(dfn));
memset(col,0,sizeof(col));
memset(low,0,sizeof(low));
timi=0;cnt=0;tot=0;
memset(hd,0,sizeof(hd));
ff=0;
}
return 0;
}
最后要请jq吃满汉全席,让她拒绝不开心