2-SAT 知识小结
问题:
有 个变量,每一个变量都是 类型的,除了这 个变量以外,我们还有 个关系表达式,关系表达式差不多是这样的:
& (注意每个表达式只会有两个变量)
问给出 个关系表达式后,能否给这 个变量找出一个赋值的方法,使得满足所有的表达式;
建边方式:
核心问题只需要考虑有没有解;
对于每个变量都只有两种取值:,那么对于每个点,我们把每个变量拆成 点和 点;
假如我们有一个关系: & ;
说明 和 中一定有一个为 ;
那么我们可以从 的 连向 的 ,从 的 连向 的 ;
要注意只有关系明确的时候才能建边;
解释一下为什么这么连边:
如果 的值为 ,那么如果我们 的值再为 的话,就不满足 “ & ” 这个式子了,所以如果 为 的话是能明确推出 为 的;
我们可以从 的 向 的 连边吗?这样也能满足关系式;
显然不能,因为如果 为 的话, 为 或 都是可以的,这不是明确的关系,我们不能建边;
所以一般的建边方式为:
若 “ 或 ” 中至少有一个满足,那么我们建两条有向边:
可以简单总结为:其中一个不成立则另一个一定成立(这是明确的关系);
如果一个变量必须等于 ,那么我们从这个点的 连向这个点的 ,表示我们从 走也会走到 ,也就是说只能等于 ;
至于建边的时候怎么给点编号嘛,自定义喽,不过我建议大家这样编号(下面的代码都是这么编号的):
这样的话对于一个变量 ,编号为 的点代表了这个变量的 点,编号为 的点代表了这个变量的 点;
判解方式:
这么判断是否有解?
无解的情况:某一个变量的 能走到 ,从 也能走到 ,也就是说,某一个变量的两个取值在同一个强连通分量内的话,就说明无解。
求强联通分量的话常用的方法是 。
否则就是有解的情况,然后它一般让你输出一个给所有变量赋值的方法,使其满足所有的关系式;
那么怎么给变量赋值呢?我们来看一个图:
我们发现,从 的 出发会走到 的 ,也就是说 现在只能等于 了;同理从 的 出发能走到 的 ,说明 只能等于 ;
解释一下吧:
如果我们不将 赋值为 ,而是赋值为 ,那会发生什么呢?
由 的 是能明确推出 是为 的,但是又有 的 能明确推出 为 ,这与刚刚我们将 赋值为 是相矛盾的;(你也可以这么理解:假如你在解方程,实在是解不出来了,你就来了个特殊值法,将 代入 试试看,结果解出来等式不成立,就说明 );
有了这个性质,就说明在有解的情况下,一个变量的两个取值是有前后推导关系的,也就是一个取值直接或间接的指向了另一个取值;
我们所要选的就是被指向的这个取值,不然会产生像上例那样的矛盾;
在拓扑序上的表现为:我们要在两种取值中选择拓扑序较大的那个值;
所以我们接下来要:缩点 + 拓扑 + 染色
其实我们在 的时候就已经求出了强联通分量的拓扑序了,只不过是反序;
(以我的理解是拓扑序越大的点在一棵树上是越靠近叶节点的,然后越靠近叶节点的那些节点在 的时候是越早被缩点的,所以拓扑序越大的点其所在强联通分量编号越小)
那么我们只要取两个取值中强联通分量编号较小的所对应的值就可以了;(这是保证不会错的,因为有时候两个值取哪个都行)
上代码(P4782 【模板】2-SAT 问题):
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<vector> 6 using namespace std; 7 long long read() 8 { 9 char ch=getchar(); 10 long long a=0,x=1; 11 while(ch<'0'||ch>'9') 12 { 13 if(ch=='-') x=-x; 14 ch=getchar(); 15 } 16 while(ch>='0'&&ch<='9') 17 { 18 a=(a<<1)+(a<<3)+(ch-'0'); 19 ch=getchar(); 20 } 21 return a*x; 22 } 23 const int N=4e6+5; 24 int n,m,a,b,x,y,tim,top,edge_sum,scc_sum; 25 int dfn[N],low[N],st[N],vis[N],scc[N],head[N]; 26 struct node 27 { 28 int to,next; 29 }A[N]; 30 void add(int from,int to) 31 { 32 edge_sum++; 33 A[edge_sum].next=head[from]; 34 A[edge_sum].to=to; 35 head[from]=edge_sum; 36 } 37 void tarjan(int u) 38 { 39 dfn[u]=low[u]=++tim; 40 st[++top]=u; 41 vis[u]=1; 42 for(int i=head[u];i;i=A[i].next) 43 { 44 int v=A[i].to; 45 if(!dfn[v]) 46 { 47 tarjan(v); 48 low[u]=min(low[u],low[v]); 49 } 50 else if(vis[v]) low[u]=min(low[u],dfn[v]); 51 } 52 if(dfn[u]==low[u]) 53 { 54 scc_sum++; 55 while(st[top]!=u) 56 { 57 scc[st[top]]=scc_sum; 58 vis[st[top]]=0; 59 top--; 60 } 61 scc[st[top]]=scc_sum; 62 vis[st[top]]=0; 63 top--; 64 } 65 } 66 int main() 67 { 68 n=read();m=read(); 69 for(int i=1;i<=m;i++) 70 { 71 a=read();x=read(); //第a个数为x或第b个数为y 72 b=read();y=read(); 73 if(x==0&&y==0) //"如果第a个数为0或第b个数为0"至少满足其一 74 { 75 add(a+n,b); //a=1则b=0 76 add(b+n,a); //b=1则a=0 77 } 78 if(x==0&&y==1) //"如果第a个数为0或第b个数为1"至少满足其一 79 { 80 add(a+n,b+n); //a=1则b=1 81 add(b,a); //b=0则a=0 82 } 83 if(x==1&&y==0) //"如果第a个数为1或第b个数为0"至少满足其一 84 { 85 add(a,b); //a=0则b=0 86 add(b+n,a+n); //b=1则a=1 87 } 88 if(x==1&&y==1) //"如果第a个数为1或第b个数为1"至少满足其一 89 { 90 add(a,b+n); //a=0则b=1 91 add(b,a+n); //b=0则a=1 92 } 93 } 94 for(int i=1;i<=2*n;i++) //对每个变量的每种取值进行tarjan 95 { 96 if(!dfn[i]) tarjan(i); 97 } 98 for(int i=1;i<=n;i++) //判断无解的情况 99 { 100 if(scc[i]==scc[i+n]) //同一变量的两种取值在同一强联通分量里,说明无解 101 { 102 printf("IMPOSSIBLE\n"); 103 return 0; 104 } 105 } 106 printf("POSSIBLE\n"); //否则就是有解 107 for(int i=1;i<=n;i++) 108 { 109 if(scc[i]>scc[i+n]) printf("1 "); //强联通分量编号越小 -> 拓扑序越大 -> 越优 110 else printf("0 "); 111 } 112 return 0; 113 }
还有一个几乎和模板题一样的水题,双倍经验,双倍欢乐!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】