关于2-sat的建图方法及解决方案
2 - SAT:
一个事物具有两面性,并且与其它事物存在约束关系
注意在建图的时候 不仅可以是在每次给出的两个间建边,还可以与其他的点建边
-------------------------------------------------对于2-sat问题的描述-------------------------------------------------
给出一个序列,每个数是一个bool值,给出一些限制关系,得到最终的可行解的问题叫做适应性问题,也就是sat问题,2-sat问题就是给出的限制最多是两两元素之间的限制。
这种适应性问题的解决,同样是能够抽象为我们已知的图论模型的。
--------------------------------------------------2-sat问题的建图方法--------------------------------------------------
1.我们利用一条有向边<i,j>,来表示选i的情况下,一定要选j;
2.用i表示某个点是true,那么i'表示某个点是false
3.因为限制的两两之间的关系,所以我们可以通过逻辑关系来建边:
在一些确定的关系中,要以确定的关系建边,可以为任意值的不建,只建有限制的(例 poj 2296 ),这种类型 要注意列出所有情况
还有 就是要明确i和~i是什么 例如poj2296 是正方形在点的上边 还是下边 hdu1814 是i去还是i^1去 而不是 i去还是不去
若图中存在有向边i->j,则表示若选了i必须选j
默认下面的x y都为1 也就是选择~~
And 结果为1:建边 ~x->x, ~y->y (两个数都为1)
And 结果为0:建边 y->~x , x->~y(两个数至少有一个为0)
OR 结果为1:建边 ~x->y , ~y->x(两个数至少有一个为1)
OR 结果为0:建边 x->~x , y->~y(两个数都为0)
XOR 结果为1:建边 x->~y , ~x->y , ~y->x , y -> ~x (两个数一个为0,一个为1)
XOR 结果为0:建边 x->y , ~x->~y , y->x, ~y->~x(两个数同为1或者同为0)
这么建图之后,会出现一个有向图,这个有向图会导致一个连通环,导致某个点一旦选取,那么这条链上的所有点都要被选中。如果我们找到一个强连通分量,那么这个强连通分量当中的点,如果选取必须全部选取,不选取的话一定是全部不选取,所以只要满足这个有向图中连通的点不会导致i和i'同时被选取,如果不存在矛盾,那么当前问题就是有解的。但是往往在求解过程中,我们要求的解会要求一些性质,所以提供以下几种解决方案。
------------------------------------------------2-sat问题的解决方案--------------------------------------------------------
1.求字典序最小的解的方法:
暴力dfs求解(复杂度O(N*M))
就是蓝书上的代码就是了 栈中的顺序即保证了字典序最小
2.判断当前的2-sa问题t是否有解
tarjan强连通缩点,加判断(复杂度O(N+M))
就是判断u --> v 是否在同一个连通分量中
具体讲解见 伍昱:《由对称性解2-SAT问题》
for(int i = 0; i < 2 * n; i++) if(!vis[i]) tarjan (i); for(int i = 0; i < n; i++) if(sccno[i << 1] == sccno[i << 1 | 1] ) return false; return true;
3.求出当前的2-sat问题的任意一组解
tarjan强连通缩点 + 反向建边 + 拓扑排序(染色) + 构建一组解(复杂度O(N+M))
选择一个没有被染色的点u 将其染成1 把所有与u点矛盾的点v和v的子孙染成2 不断重复操作 直到没有点可以染色而止
例题
Wedding
n对夫妻去参加婚礼
但有m对人有通奸关系,(也有可能同性),不能让新娘看到有通奸关系的两个人坐在一边,n对夫妻必须不能坐在一边
新娘带着超豪华的头饰,以至于她不能看到和她一遍的人,现在求一组和新娘坐在一边的人的解
意思就是 有通奸关系的可以一边一个 ,也可以都坐在新娘的那边 (因为她看不到)
也就是至少有一个1
#include <iostream> #include <cstdio> #include <sstream> #include <cstring> #include <map> #include <cctype> #include <set> #include <vector> #include <stack> #include <queue> #include <algorithm> #include <cmath> #include <bitset> #define rap(i, a, n) for(int i=a; i<=n; i++) #define rep(i, a, n) for(int i=a; i<n; i++) #define lap(i, a, n) for(int i=n; i>=a; i--) #define lep(i, a, n) for(int i=n; i>a; i--) #define rd(a) scanf("%d", &a) #define rlld(a) scanf("%lld", &a) #define rc(a) scanf("%c", &a) #define rs(a) scanf("%s", a) #define pd(a) printf("%d\n", a); #define plld(a) printf("%lld\n", a); #define pc(a) printf("%c\n", a); #define ps(a) printf("%s\n", a); #define MOD 2018 #define LL long long #define ULL unsigned long long #define Pair pair<int, int> #define mem(a, b) memset(a, b, sizeof(a)) #define _ ios_base::sync_with_stdio(0),cin.tie(0) //freopen("1.txt", "r", stdin); using namespace std; const int maxn = 10010, INF = 0x7fffffff, LL_INF = 0x7fffffffffffffff; int n, m; vector<int> G[maxn]; vector<int> f[maxn]; int vis[maxn], low[maxn], sccno[maxn], scc_clock, scc_cnt; int head[maxn], cnt, in[maxn],col[maxn], cft[maxn]; stack<int> S; void dfs(int u) { low[u] = vis[u] = ++scc_clock; S.push(u); for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!vis[v]) { dfs(v); low[u] = min(low[u], low[v]); } else if(!sccno[v]) low[u] = min(low[u], vis[v]); } if(vis[u] == low[u]) { scc_cnt++; for(;;) { int x = S.top(); S.pop(); sccno[x] = scc_cnt; if(x == u) break; } } } void bfs() { queue<int> Q; for(int i = 1; i <= scc_cnt; i++) //把入度为0的加入的队列中 if(!in[i]) Q.push(i); while(!Q.empty()) { int u = Q.front(); Q.pop(); if(!col[u]) //染色 { col[u] = 1; col[cft[u]] = 2; } for(int i = 0; i < f[u].size(); i++) { int v = f[u][i]; in[v]--; //删除与u相关的边 if(!in[v]) Q.push(v); } } } void init() { for(int i = 0; i < maxn; i++) f[i].clear(), G[i].clear(); while(!S.empty()) S.pop(); mem(head, -1); mem(vis, 0); mem(sccno, 0); mem(in, 0); mem(col, 0); cnt = scc_clock = scc_cnt = 0; } int main() { while(cin >> n >> m && n + m) { init(); int x, y; char a, b; for(int i = 0; i < m; i++) { scanf("%d%c%d%c", &x, &a, &y, &b); if(a == 'w') x = 2 * x; else if(a == 'h') x = 2 * x + 1; if(b == 'w') y = 2 * y; else if(b == 'h') y = 2 * y + 1; G[x^1].push_back(y); G[y^1].push_back(x); } G[0].push_back(1); for(int i = 0; i < 2 * n; i++) { if(!vis[i]) dfs(i); } int flag = 0; for(int i = 0; i < n * 2; i+=2) //判断是否有解 { int u = sccno[i], v = sccno[i + 1]; if(u == v) { flag = 1; cout << "bad luck" << endl; break; } cft[u] = v; //同时标记对立点 cft[v] = u; } if(flag) continue; for(int i = 0; i < n * 2; i++) //建反向边 { for(int j = 0; j < G[i].size(); j++) { int u = sccno[i], v = sccno[G[i][j]]; if(u != v) { f[v].push_back(u); in[u]++; } } } bfs(); //拓扑排序 for(int i = 2; i < n * 2; i+=2) { if(i != 2) cout << " "; if(col[sccno[i]] == col[sccno[0]]) cout << i/2 << "w"; else cout << i/2 << "h"; } cout << endl; } return 0; }
参考文献:
https://blog.csdn.net/qq_24451605/article/details/47126143
https://blog.csdn.net/u012915516/article/details/48442265
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本