满汉全席(洛谷-P4171)

题目描述

满汉全席是中国最丰盛的宴客菜肴,有许多种不同的材料透过满族或是汉族的料理方式,呈现在數量繁多的菜色之中。由于菜色众多而繁杂,只有极少數博学多闻技艺高超的厨师能够做出满汉全席,而能够烹饪出经过专家认证的满汉全席,也是中国厨师最大的荣誉之一。世界满汉全席协会是由能够料理满汉全席的专家厨师们所组成,而他们之间还细分为许多不同等级的厨师。

为了招收新进的厨师进入世界满汉全席协会,将于近日举办满汉全席大赛,协会派遣许多会员当作评审员,为的就是要在參赛的厨师之中,找到满汉料理界的明日之星。

大会的规则如下:每位參赛的选手可以得到n 种材料,选手可以自由选择用满式或是汉式料理将材料当成菜肴。

大会的评审制度是:共有m 位评审员分别把关。每一位评审员对于满汉全席有各自独特的見解,但基本见解是,要有兩样菜色作为满汉全席的标志。如某评审认为,如果没有汉式东坡肉跟满式的涮羊肉锅,就不能算是满汉全席。但避免过于有主見的审核,大会规定一个评审员除非是在认为必备的两样菜色都没有做出來的狀况下,才能淘汰一位选手,否则不能淘汰一位參赛者。

换句话說,只要參赛者能在这兩种材料的做法中,其中一个符合评审的喜好即可通过该评审的审查。如材料有猪肉,羊肉和牛肉时,有四位评审员的喜好如下表:

评审一 评审二 评审三 评审四 
满式牛肉 满式猪肉 汉式牛肉 汉式牛肉 
汉式猪肉 满式羊肉 汉式猪肉 满式羊肉 

如參赛者甲做出满式猪肉,满式羊肉和满式牛肉料理,他将无法满足评审三的要求,无法通过评审。而參赛者乙做出汉式猪肉,满式羊肉和满式牛肉料理,就可以满足所有评审的要求。

但大会后來发现,在这样的制度下如果材料选择跟派出的评审员没有特别安排好的话,所有的參赛者最多只能通过部分评审员的审查而不是全部,所以可能会发生没有人通过考核的情形。

如有四个评审员喜好如下表时,则不论参赛者采取什么样的做法,都不可能通过所有评审的考核:

评审一 评审二 评审三 评审四 
满式羊肉 满式猪肉 汉式羊肉 汉式羊肉 
汉式猪肉 满式羊肉 汉式猪肉 满式猪肉 

所以大会希望有人能写一个程序來判断,所选出的m 位评审,会不会发生 没有人能通过考核的窘境,以便协会组织合适的评审团。

输入输出格式

输入格式:

第一行包含一个数字 K,代表测试文件包含了K 组资料。

每一组测试资料的第一行包含兩个数字n 跟m(n≤100,m≤1000),代表有n 种材料,m 位评审员。

为方便起見,材料舍弃中文名称而给予编号,编号分别从1 到n。

接下來的m 行,每行都代表对应的评审员所拥有的兩个喜好,每个喜好由一个英文字母跟一个数字代表,如m1 代表这个评审喜欢第1 个材料透过满式料理做出來的菜,而h2 代表这个评审员喜欢第2 个材料透过汉式料理做出來的菜。

每个测试文件不会有超过50 组测试资料

输出格式:

每笔测试资料输出一行,如果不会发生没有人能通过考核的窘境,输出GOOD;否则输出BAD(大写字母)。

输入输出样例

输入样例#1:

2
3 4
m3 h1
m1 m2
h1 h3
h3 m2
2 4
h1 m2
m2 m1
h1 h2
m1 h2

输出样例#1:
GOOD
BAD

思路:

对于每组数据,将 n 个材料拆分为两个点,用 i 来表示为满式,用 i+n 来表示为汉式,0、1 来表示不选择与选择,根据评委的限制条件,对于两个材料 a、b 有:

  • 当 a 是满式时,则 b 必为汉式,有:<a,0,b,0>、<b,1,a,1>,即连边 (a+n,b+n)、(b,a)
  • 当 a 是满式时,则 b 必为满式,有:<a,0,b,1>、<b,0,a,1>,即连边 (a+n,b)、(b+n,a)
  • 当 a 是汉式时,则 b 必为汉式,有:<a,1,b,0>、<b,1,a,0>,即连边 (a,b+n)、(b,a+n)
  • 当 a 是汉式时,则 b 必为满式,有:<a,1,b,1>、<b,0,a,0>,即连边 (a,b)、(b+n,a+n)

建完图后,跑一遍 2-SAT 的版子即可

源代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define Exp 1e-9
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
const int MOD = 1E9+7;
const int N = 1000000+5;
const int dx[] = {-1,1,0,0,-1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;

struct Edge{
    int to,next;
}edge[N*2];
int head[N],tot;
int n,m;
int dfn[N],low[N];
bool vis[N];//标记数组
int scc[N];//记录结点i属于哪个强连通分量
int block_cnt;//时间戳
int sig;//记录强连通分量个数
stack<int> S;
void init(){
    tot=0;
    sig=0;
    block_cnt=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(scc,0,sizeof(scc));
}
void addEdge(int from,int to){
    edge[++tot].to=to;
    edge[tot].next=head[from];
    head[from]=tot;
}
void Tarjan(int x) {
    vis[x]=true;
    dfn[x]=low[x]=++block_cnt;//每找到一个新点,纪录当前节点的时间戳
    S.push(x);//当前结点入栈

    for(int i=head[x]; i!=-1; i=edge[i].next) { //遍历整个栈
        int y=edge[i].to;//当前结点的下一结点
        if(!dfn[y]) {
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(vis[y])
            low[x]=min(low[x],dfn[y]);
    }

    if(dfn[x]==low[x]) { //满足强连通分量要求
        sig++;//记录强连通分量个数

        while(true) { //记录元素属于第几个强连通分量
            int temp=S.top();
            S.pop();
            vis[temp]=false;
            scc[temp]=sig;
            if(temp==x)
                break;
        }
    }
}
bool twoSAT(){
    for(int i=1;i<=2*n;i++)//找强连通分量
        if(!dfn[i])
            Tarjan(i);
    for(int i=1;i<=n;i++)
        if(scc[i]==scc[i+n])//条件a与!a属于同一连通分量,无解
            return false;
    return true;
}
int main() {
    int t;
    scanf("%d",&t);
    while(t--){
        init();

        scanf("%d%d",&n,&m);
        while(m--) {
            char s1[10],s2[10];
            scanf("%s%s",s1,s2);

            int a=0,b=0;
            int k=1;
            while(s1[k]>='0'&&s1[k]<='9')
                a=a*10+(s1[k++]-'0');
            k=1;
            while(s2[k]>='0'&&s2[k]<='9')
                b=b*10+(s2[k++]-'0');

            if(s1[0]=='m'){
                if(s2[0]=='h'){
                    addEdge(a+n,b+n);
                    addEdge(b,a);
                }
                else if(s2[0]=='m'){
                    addEdge(a+n,b);
                    addEdge(b+n,a);
                }
            }
            else if(s1[0]=='h'){
                if(s2[0]=='h'){
                    addEdge(a,b+n);
                    addEdge(b,a+n);
                }
                else if(s2[0]=='m'){
                    addEdge(a,b);
                    addEdge(b+n,a+n);
                }
            }
        }
        bool flag=twoSAT();
        if(!flag)
            printf("BAD\n");
        else
            printf("GOOD\n");
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值