2-SAT问题
现有一个由N个布尔值组成的序列A,给出一些限制关系,比如A[x] AND A[y]=0、A[x] OR A[y] OR A[z]=1等,要确定A[0..N-1]的值,使得其满足所有限制关系。这个称为SAT问题,特别的,若每种限制关系中最多只对两个元素进行限制,则称为2-SAT问题。
因为如果元素多个,算不出来,这是NP问题
https://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html
洛谷的讲解非常好:https://www.luogu.com.cn/problemnew/solution/P4782
解决方法:强连通分量+拓扑排序
建图:通过限制关系建图
ps. 注意点的个数为2*N,因为x为i,-x为i+n
建好图之后,求出强连通分量SCC,一个SCC内部都是互相依赖的,所以一个SCC以内不能出现夫妻关系(x与-x),如果所有的SCC内部都没有夫妻关系,就有合法的组合
把每个SCC看成是一个点,构成了DAG图,进行反图的拓扑排序,再选中点时排除图中相矛盾的点,就能找到合法的组合。
洛谷的讲解:
最后的输出为col[i]>col[i+n]
P4782 【模板】2-SAT 问题
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=4e6+10; const int INF=0x3fffffff; typedef long long LL; //2-SAT模板问题 //洛谷的讲解非常好:https://www.luogu.com.cn/problemnew/solution/P4782 int col[maxn],head[maxn]; int low[maxn],num[maxn],dfn,ans; int vis[maxn],sta[maxn],top; int n,m; struct node{ int to,next; }ed[maxn]; int cnt; void add(int f,int t){ ed[++cnt].to=t; ed[cnt].next=head[f]; head[f]=cnt; } /* void create(){ scanf("%d %d",&n,&m); int x,y,a,b; for(int i=0;i<m;i++){ scanf("%d %d %d %d",&x,&a,&y,&b); if(a&&b){ // a, b 都真,-a -> b, -b -> a g[a+n].push_back(b); g[b+n].push_back(a); } else if(!a&&b){// a 假 b 真,a -> b, -b -> -a g[a].push_back(b); g[b+n].push_back(a+n); } else if(a&&!b){ // a 真 b 假,-a -> -b, b -> a g[a+n].push_back(n+b); g[b].push_back(a); } else if(!a&&!b){ // a, b 都假,a -> -b, b -> -a g[a].push_back(b+n); g[b].push_back(a+n); } } } */ /* 更简单的方式 n = read(), m = read(); for (int i = 0; i < m; ++i) { int a = read(), va = read(), b = read(), vb = read(); g[a + n * (va & 1)].push_back(b + n * (vb ^ 1)); g[b + n * (vb & 1)].push_back(a + n * (va ^ 1)); } */ void tarjin(int u,int fa){ low[u]=num[u]=++dfn; sta[++top]=u; vis[u]=1; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(v==fa) continue; if(!num[v]){ tarjin(v,u); low[u]=min(low[u],low[v]); } else if(vis[v]){ low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ ans++; while(sta[top]!=u){ col[sta[top]]=ans; vis[sta[top]]=0; top--; } col[sta[top]]=ans; vis[sta[top]]=0; top--; //给u } } int main(){ scanf("%d %d",&n,&m); int a,b,x,y; for(int i=0;i<m;i++){ scanf("%d %d %d %d",&a,&x,&b,&y); if(x==0&&y==0) //"如果第a个数为0或第b个数为0"至少满足其一 { add(a+n,b); //a=1则b=0 add(b+n,a); //b=1则a=0 } if(x==0&&y==1) //"如果第a个数为0或第b个数为1"至少满足其一 { add(a+n,b+n); //a=1则b=1 add(b,a); //b=0则a=0 } if(x==1&&y==0) //"如果第a个数为1或第b个数为0"至少满足其一 { add(a,b); //a=0则b=0 add(b+n,a+n); //b=1则a=1 } if(x==1&&y==1) //"如果第a个数为1或第b个数为1"至少满足其一 { add(a,b+n); //a=0则b=1 add(b,a+n); //b=0则a=1 } } for(int i=1;i<=(n<<1);i++){ if(!num[i]) tarjin(i,0); } //就可以得到答案了,如果两个取值是在同一个SCC中就不可能 for(int i=1;i<=n;i++){ if(col[i]==col[i+n]){ printf("IMPOSSIBLE "); return 0; } } printf("POSSIBLE "); for(int i=1;i<=n;i++){ printf("%d ",col[i]>col[i+n]); //注意这个输出 } return 0; }
P4171 [JSOI2010]满汉全席
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=4010; const int INF=0x3fffffff; typedef long long LL; int readd(){ int f=1,x=0; char ss=getchar(); while(ss<'0'||ss>'9') { if(ss=='-') f=-1; ss=getchar(); } while(ss>='0'&&ss<='9'){ x=x*10+ss-'0'; ss=getchar(); } return f*x; } int n,m,k; int head[maxn],low[maxn],num[maxn],col[maxn],st[maxn],vis[maxn]; int top,ans,cnt,dfn; struct node{ int to,next; }ed[maxn]; void adde(int f,int t){ ed[++cnt].to=t; ed[cnt].next=head[f]; head[f]=cnt; } void tarjin(int u,int fa){ low[u]=num[u]=++dfn; vis[u]=1; st[++top]=u; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(!num[v]){ tarjin(v,u); low[u]=min(low[u],low[v]); } else if(vis[v]){ low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ ans++; do{ vis[st[top]]=0; col[st[top]]=ans; top--; }while(u!=st[top]); } } void inti(){ memset(head,0,sizeof(head)); memset(vis,0,sizeof(vis)); memset(low,0,sizeof(low)); memset(st,0,sizeof(st)); memset(num,0,sizeof(num)); memset(col,0,sizeof(col)); cnt=0,dfn=0,top=0,ans=0; } int main(){ scanf("%d",&k); while(k--){ inti(); scanf("%d %d",&n,&m); char str1[10],str2[10]; for(int i=0;i<m;i++){ scanf("%s %s",str1,str2); int a=0,b=0; for(int j=1;j<strlen(str1);j++) a=a*10+(str1[j]-'0'); for(int j=1;j<strlen(str2);j++) b=b*10+(str2[j]-'0'); if(str1[0]=='m'&&str2[0]=='m'){ //0 0 adde(a+n,b); //1 0 adde(b+n,a); //1 0 } else if(str1[0]=='m'&&str2[0]=='h'){ //0 1 adde(a+n,b); //1 0 adde(b,a); //0 0 } else if(str1[0]=='h'&&str2[0]=='m'){ //1 0 adde(a,b); //0 0 adde(b+n,a+n); //1 1 } else if(str1[0]=='h'&&str2[0]=='h'){ // 1 1 adde(a,b+n); adde(b,a+n) ; //0 1 } } for(int i=1;i<=(n<<1);i++){ if(!num[i]) tarjin(i,0); } bool flag=true; for(int i=1;i<=n;i++){ if(col[i]==col[i+n]){ flag=0; break; } } if(flag) printf("GOOD "); else printf("BAD "); } return 0; }
P5782 [POI2001]和平委员会
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=40010; const int INF=0x3fffffff; typedef long long LL; int head[maxn],vis[maxn],low[maxn],num[maxn],col[maxn]; int ans,cnt,top,dfn,n,m; stack<int> st; struct node{ int t,next; }ed[maxn]; void add(int u,int v){ ed[++cnt].next=head[u]; ed[cnt].t=v; head[u]=cnt; } void tarjin(int u){ low[u]=num[u]=++dfn; st.push(u); vis[u]=1; for(int i=head[u];i;i=ed[i].next) { int v=ed[i].t; if(!num[v]){ tarjin(v); low[u]=min(low[u],low[v]);} else if(vis[v]) low[u]=min(low[u],num[v]); } if(low[u]==num[u]) { int v; ans++; do{ v=st.top(); st.pop(); vis[v]=0; col[v]=ans; } while(v!=u); } } int chan(int x){ return ((x%2)? x+1:x-1); } int main(){ scanf("%d %d",&n,&m); int x,y; for(int i=0;i<m;i++){ scanf("%d %d",&x,&y); add(x,chan(y)); add(y,chan(x)); } for(int i=1;i<=(n<<1);i++){ if(!num[i]) tarjin(i); } bool flag=1; for(int i=1;i<=2*n;i++){ //每个党派有两个代表,所以要乘 2 if(i%2==1&&col[i]==col[i+1]) { flag=0; break; } } if(!flag) printf("NIE "); else{ for(int i=1;i<=2*n;i++){ if(col[i]<col[chan(i)]){ printf("%d ",i); } } } return 0; }