BZOJ1823 JSOI2010 满汉全席 2-SAT

题意:有N种食材,每种食材有两种烹饪方法,存在M组约束关系——A用哪种手法烹饪,B用哪种手法烹饪——问是否存在烹饪序列满足要求。

题解:

所谓2-SAT问题,是指我们有多个集合,每个集合里只有两个元素A,A'(或者是B,B' C,C'……),某些集合之间存在约束关系,比如说选了A就必须选B,选了B'就不能选C之类的。

根据题目要求建边,如果有一条有向边<u,v>,那么其代表选择了u就必须选v,例如:

选了A就必须选B<=>存在边<A,B>

选了B'就不能选C<=>存在边<B',C>和<B,C'>

然后Tarjan缩点,如果有同属一个集合的两个点在同一个SCC中,那么给定的约束关系一定无法满足,2-SAT无解。

否则,将缩点后的图中的边全部反向,跑拓扑排序,每访问一个节点就将这个节点的互斥节点(两节点中的元素均在同一集合中,也就是说一个节点有A,那么其对立节点中就含有A')打上"不可访问标记",在反向图中将互斥节点所能到达的节点全部打上"不可访问标记"。这样跑出来的拓扑序中所含的元素就是一个合法方案。

(至于为何是打不可访问的标记,因为在正向图中很有可能跑到一个岔路口,而这个地方的下一步是不能确定的。而反向图中一个节点不可访问,则其后继节点就全部不可访问,因为后继节点一旦可以访问,根据正向边的“一定性”该节点就必须可以访问。)

特别注意的是如果存在条件:A A'中只能选择A,那么连边<A,A'>,这个一是为了让A先被访问,而是为了防止存在<A',A>的路径。

#include <stack>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN=1000+2;
const int MAXM=20000+2;
struct HASH{
    int u;
    HASH *next;
    HASH(){}
    HASH(int _u,HASH *_next):u(_u),next(_next){}
}*table[MAXN],mem[2*MAXM];
int N,M,K,cnt,dfn[MAXN],low[MAXN],depth,scc,f[MAXN];
char s1[4+2],s2[4+2];
bool flag[MAXN];
stack<int> s;

void Insert(int u,int v){ table[u]=&(mem[cnt++]=HASH(v,table[u]));}

void Tarjan(int x){
    dfn[x]=low[x]=++depth,flag[x]=1,s.push(x);
    for(HASH *p=table[x];p;p=p->next)
        if(!dfn[p->u]) Tarjan(p->u),low[x]=min(low[x],low[p->u]);
        else if(flag[p->u]) low[x]=min(low[x],dfn[p->u]);

    if(dfn[x]==low[x]){
        int t=-1;scc++;
        while(t!=x){
            t=s.top(),s.pop(),flag[t]=0;
            f[t]=scc;
        }
    }
}

bool Check(){
    for(int i=1;i<=N;i++)
        if(f[i]==f[i+N]) return 0;
    return 1;
}

int Deal(char *s){
    int ret=0;
    for(int i=1,j=strlen(s);i<j;i++) ret*=10,ret+=s[i]-'0';
    return ret;
}

int main(){
    scanf("%d",&K);
    while(K--){
        cnt=depth=scc=0;
        memset(f,0,sizeof(f));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(table,0,sizeof(table));

        scanf("%d%d",&N,&M);
        for(int i=1,a,b;i<=M;i++){
            scanf("%s%s",s1,s2);
            a=Deal(s1),b=Deal(s2);
            if(s1[0]=='m') a+=N;
            if(s2[0]=='m') b+=N;

            if(a<=N) Insert(a+N,b);
            else Insert(a-N,b);
            if(b<=N) Insert(b+N,a);
            else Insert(b-N,a);
        }

        for(int i=1;i<=2*N;i++)
            if(!dfn[i]) Tarjan(i);
        if(Check()) printf("GOOD\n");
        else printf("BAD\n");
    }

    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/WDZRMPCBIT/p/6481234.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值