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; }