2-SAT 问题

news/2024/10/6 2:07:47/文章来源:https://www.cnblogs.com/Heartquakes/p/18287338

2-SAT 问题

模型

\(n\) 个布尔类型的变量 \(x_1, x_2, \ldots, x_n\),有 \(m\) 条限制形如 \(x_i \space [\operatorname{or}/\operatorname{and}]\space x_j=[1/0]\).
求一组符合要求的解。

核心问题只需要考虑有没有解。

对于每个变量都只有两种取值:\(0/1\),那么把每个变量拆成 \(0\)\(1\) 两个点。

一般地,对 \(x, y\) 建边时,对 \(x\)\(0, 1\) 两种取值分别考虑 \(y\) 的取值,若 \(y\) 的取值一定,则从 \(x\) 对应取值的点向 \(y\) 对应取值的点连边。

本质上是找到确定的变量间关系并判断是否有关系冲突。

连上边后,通过 Tarjan 找到所有强联通分量。同一强连通分量内的变量值一定是相等的,所以只需判断表示 \(x = 1\)\(x = 0\) 的节点是否在同一个强联通分量里即可。如果是,则无解,反之有解。

在构造解时,对每个节点 \(x\),我们会选择两种取值中拓扑序较大的点,这样就不会产生从一种取值推出另一种取值的情况(比如从 \(x=1\) 推出 \(x=0\))。

注意到在 Tarjan 得到的搜索树上,拓扑序越大的点越远离树根,强联通分量编号也越小。所以直接选择表示两种取值的节点中强联通分量的编号较小的点即可。


例题

P4782 【模板】2-SAT

标准的 2-SAT 板子题,细节见代码。

#include <bits/stdc++.h>
using namespace std;const int N = 2e6 + 5;int n, m, a, b, x, y, dfn[N], low[N], h, ans, tot, idx, bel[N];
bool vis[N];
vector<int> g[N];
stack<int> stk;void tarjan(int u, int fa)
{low[u] = dfn[u] = ++idx;stk.push(u), vis[u] = 1;for(auto v : g[u]){if(!dfn[v]){tarjan(v, u);low[u] = min(low[u], low[v]);}else if(vis[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){tot++;do{h = stk.top(); stk.pop();vis[h] = 0, bel[h] = tot;} while(h != u);}return;
}int main()
{ios :: sync_with_stdio(false);cin.tie(0), cout.tie(0);cin >> n >> m;for(int i = 1; i <= m; i++){/*限制:arr[a] = x 或 arr[b] = y建边:形如“若 arr[a] = ...,则 arr[b] = ...”,必须是确定的关系。用 x (x <= n) 代表值为 0,x + n 代表值为 1*/cin >> a >> x >> b >> y;if(x == 0 && y == 0){g[a + n].push_back(b);      // a = 1 -> b = 0g[b + n].push_back(a);      // b = 1 -> a = 0}else if(x == 0 && y == 1){g[a + n].push_back(b + n);  // a = 1 -> b = 1g[b].push_back(a);          // b = 0 -> a = 0}else if(x == 1 && y == 0){g[a].push_back(b);          // a = 0 -> b = 0g[b + n].push_back(a + n);  // a = 1 -> b = 1}else{g[a].push_back(b + n);      // a = 0 -> b = 1g[b].push_back(a + n);      // b = 0 -> a = 1}}for(int i = 1; i <= 2 * n; i++)if(!dfn[i]) tarjan(i, 0);for(int i = 1; i <= n; i++){if(bel[i] == bel[i + n]){cout << "IMPOSSIBLE";return 0;}}cout << "POSSIBLE\n";for(int i = 1; i <= n; i++)cout << (bel[i] > bel[i + n]) << ' ';return 0;
}

P4171 [JSOI2010] 满汉全席

和模板题一样,不过要用 \(0, 1\) 表示 \(\texttt{m}\)\(\texttt{h}\).

初始化时要注意有 \(2n\) 个点,而不是 \(n\) 个。

#include <bits/stdc++.h>
using namespace std;const int N = 2e6 + 5;int n, m, a, b, x, y, dfn[N], low[N], h, ans, tot, idx, bel[N];
bool vis[N];
vector<int> g[N];
stack<int> stk;void tarjan(int u, int fa)
{low[u] = dfn[u] = ++idx;stk.push(u), vis[u] = 1;for(auto v : g[u]){if(!dfn[v]){tarjan(v, u);low[u] = min(low[u], low[v]);}else if(vis[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){tot++;do{h = stk.top(); stk.pop();vis[h] = 0, bel[h] = tot;} while(h != u);}return;
}void solve()
{cin >> n >> m;for(int i = 1; i <= 2 * n; i++)g[i].clear();memset(dfn, 0, sizeof dfn);memset(low, 0, sizeof low);memset(vis, 0, sizeof vis);memset(bel, 0, sizeof bel);for(int i = 1; i <= m; i++){string s1, s2;cin >> s1 >> s2;a = b = 0;for(int i = 1; i < s1.size(); i++)a = a * 10 + s1[i] - '0';for(int i = 1; i < s2.size(); i++)b = b * 10 + s2[i] - '0';	x = (s1[0] == 'm');y = (s2[0] == 'm');if(x == 0 && y == 0){g[a + n].push_back(b);g[b + n].push_back(a);}else if(x == 0 && y == 1){g[a + n].push_back(b + n);g[b].push_back(a);}else if(x == 1 && y == 0){g[a].push_back(b);g[b + n].push_back(a + n);}else{g[a].push_back(b + n);g[b].push_back(a + n);}}for(int i = 1; i <= 2 * n; i++)if(!dfn[i]) tarjan(i, 0);for(int i = 1; i <= n; i++)if(bel[i] == bel[i + n])return (void) (cout << "BAD\n");cout << "GOOD\n";return;
}int main()
{ios :: sync_with_stdio(false);cin.tie(0), cout.tie(0);int T; cin >> T;while(T--) solve();return 0;
}

P6378 [PA2010] Riddle

  • 对于“每条边至少有一个端点是关键点”的限制,按照“\(a=1 \space \operatorname{or} \space b=1\)” 的限制来连边即可。
  • 对于“每个部分恰有几个关键点”的限制,直接连边会导致边数过多,达到 \(O\left(n^2\right)\) 级别,所以考虑对建图进行一些优化。

第一条限制的本质,是在每一个部分中,把每一个点的 “真状态” 连向其余所有点的 “假状态”。并且只要保证连通即可。考虑进行前后缀优化建图,将连向除了自身的另一个状态以外的所有点,转化为连向这个点之前的前缀以及这个点之后的后缀。

image

此时,这个新图的性质已经和原图完全等价了。

直接跑 2-SAT 模板即可。

#include <bits/stdc++.h>
using namespace std;const int N = 4e6 + 5;int n, m, a, b, k, w, dfn[N], low[N], h, ans, tot, idx, bel[N], c[N];
bool vis[N];
vector<int> g[N];
stack<int> stk;void tarjan(int u, int fa)
{low[u] = dfn[u] = ++idx;stk.push(u), vis[u] = 1;for(auto v : g[u]){if(!dfn[v]){tarjan(v, u);low[u] = min(low[u], low[v]);}else if(vis[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){tot++;do{h = stk.top(); stk.pop();vis[h] = 0, bel[h] = tot;} while(h != u);}return;
}int main()
{ios :: sync_with_stdio(false);cin.tie(0), cout.tie(0);cin >> n >> m >> k;for(int i = 1; i <= m; i++){cin >> a >> b;g[a + n].push_back(b);g[b + n].push_back(a);}/*1 ~ n:       x = 1n + 1 ~ 2n:  x = 02n + 1 ~ 3n: 前缀3n + 1 ~ 4n:后缀*/for(int i = 1; i <= k; i++) // 前缀优化建图{cin >> w;for(int j = 1; j <= w; j++){cin >> c[j];g[c[j] + 2 * n].push_back(c[j] + n);g[c[j] + 3 * n].push_back(c[j] + n);if(j > 1){g[c[j - 1]].push_back(c[j] + 2 * n);g[c[j - 1] + 2 * n].push_back(c[j] + 2 * n);g[c[j]].push_back(c[j - 1] + 3 * n);g[c[j] + 3 * n].push_back(c[j - 1] + 3 * n);}}}for(int i = 1; i <= 2 * n; i++)if(!dfn[i]) tarjan(i, 0);for(int i = 1; i <= n; i++){if(bel[i] == bel[i + n]){cout << "NIE";return 0;}}cout << "TAK";return 0;
}

P3209 [HNOI2010] 平面图判定

首先,根据平面图的性质,若 \(m > 3n-6\),则图肯定不为平面图,可以直接输出 NO

将哈密顿回路画成一个圆,那么原图上不在哈密顿回路上的边可以看作是该圆的弦。

考虑圆中两条相交的弦 \((u_1, v_1), (u_2, v_2)\),由于是平面图,我们可以将其中的一条翻到圆外去(但两条弦不能同时翻到圆外去,原因是如果他们在圆内相交,那么在圆外也会相交,可以画图模拟一下)。

\(x=1/0\) 表示一条边是否在圆外,由于两条在圆内相交的弦必有一个在圆外,一个在圆内,考虑将弦视为点,使用 2-SAT 建模。

具体连边有 \((x, y'), (x', y), (y, x'), (y', x)\) 四种。

#include <bits/stdc++.h>
using namespace std;const int N = 2e3 + 5, M = 2e6 + 5;int n, m, u[M], v[M], dfn[M], low[M], h, ans, tot, idx, bel[M], a[M], mp[N][N], cnt, rk[M];
bool vis[M];
vector<int> g[M];
stack<int> stk;void tarjan(int u, int fa)
{low[u] = dfn[u] = ++idx;stk.push(u), vis[u] = 1;for(auto v : g[u]){if(!dfn[v]){tarjan(v, u);low[u] = min(low[u], low[v]);}else if(vis[v]) low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){tot++;do{h = stk.top(); stk.pop();vis[h] = 0, bel[h] = tot;} while(h != u);}return;
}void solve()
{cnt = idx = tot = 0;memset(dfn, 0, sizeof dfn);memset(low, 0, sizeof low);memset(vis, 0, sizeof vis);memset(bel, 0, sizeof bel);memset(mp, 0, sizeof mp);memset(rk, 0, sizeof rk);cin >> n >> m;for(int i = 1; i <= m; i++){cin >> u[i] >> v[i];if(u[i] > v[i]) swap(u[i], v[i]);}for(int i = 1; i <= n; i++){cin >> a[i];rk[a[i]] = i;if(i > 1){int x = a[i - 1], y = a[i];if(x > y) swap(x, y);mp[x][y] = 1;}if(i == n){int x = a[i], y = a[1];if(x > y) swap(x, y);mp[x][y] = 1;}}if(m > 3 * n - 6) return (void) (cout << "NO\n");for(int i = 1; i <= m; i++)if(!mp[u[i]][v[i]]) u[++cnt] = u[i], v[cnt] = v[i];m = cnt;for(int i = 1; i < m; i++){int x = rk[u[i]], y = rk[v[i]];if(x > y) swap(x, y);for(int j = i + 1; j <= m; j++){int xx = rk[u[j]], yy = rk[v[j]];if(xx > yy) swap(xx, yy);if(xx < x && x < yy && yy < y || x < xx && xx < y && y < yy){g[i].push_back(j + m);g[i + m].push_back(j);g[j].push_back(i + m);g[j + m].push_back(i);}}}for(int i = 1; i <= m * 2; i++)if(!dfn[i]) tarjan(i, 0);for(int i = 1; i <= 2 * m; i++) g[i].clear();for(int i = 1; i <= m; i++)if(bel[i] == bel[i + m])return (void) (cout << "NO\n");cout << "YES\n";return;
}int main()
{ios :: sync_with_stdio(false);cin.tie(0), cout.tie(0);int T; cin >> T;while(T--) solve();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/739438.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

坚果云与floccus实现Chrome书签国内跨设备、跨平台同步

本文介绍基于floccus插件与坚果云协同使用的方法,对浏览器的书签进行实时在线同步的操作~本文介绍基于floccus插件与坚果云协同使用的方法,对浏览器的书签进行实时在线同步的操作。在工作与学习中,我们时常希望在不同浏览器之间实现书签的同步;而一些传统的浏览器书签同步方…

性能监控调优

1.命令行工具 1.1 jps 查看系统JVM进程 jps [options] [hostid]optionhostid:省略则本机,监控远程需要安装jstatd1.2 jstat 查看JVM统计信息 jstat -[-t] [-h] [[]]option-t:加程序运行时间 [-h]:输出设定行数加表头信息 :查询间隔时间 count:查询总次数 结果:当ou上涨趋…

【第7个渗透靶机项目】 DerpNStink

Hack it 信息搜集 发现主机 nmap 192.168.0.17 -sS -sV -A -T5 全面扫描一下,有点有用信息访问一下但是没有用。 访问一下http服务查看源代码,发现有文件泄露下面还有个flag查看info.txt。<-- @stinky,确保使用本地 dns 更新您的主机文件,以便可以在新的 derpnstink 博客…

Linux运维工程师推荐学习的开发语言

前言:会开发的运维和不会开发的运维可以说是两个世界的运维。 个人推荐python和go,前者可以做自动化运维,后者可以深挖k8s;最近就不先演示运维服务技术的部署和架构搭建了,在深挖自动化运维,为了让现在的工作更加高效和便捷。如果有需要了解的运维服务技术,可以评论说出…

2、flask-run启动参数详解

app.py 这里 app.run(True, port=5001, host=0.0.0.0) from flask import Flask#创建flask应用对象 app = Flask(__name__)@app.route(/) # 路由 def hello_world(): # 视图函数return Hello World! # 响应给前端#添加路由和视图函数 @app.route(/index/) def index():…

从零学习的JAVAday1~day7

作为一个刚要迈入大二的预备程序员,已经学习过了c语言和c++的部分知识,在暑假期间满怀期待的开始Java的学习,希望一个暑假可以对Java的了解加深一些。 学习Java首先要学习windows电脑的cmd命令: 同时点击键盘上面的win+r键输入cmd即可进入默认的cmd面版,然后我们就可以输入…

面试官:Java线程可以无限创建吗?

哈喽,大家好🎉,我是世杰。 ⏩本次给大家介绍一下操作系统线程和Java的线程以及二者的关联1. 面试连环callJava线程可以无限创建吗? Java线程和操作系统线程有什么关联? 操作系统为什么要区分内核态和用户态?⏩要想解答这些问题,我们要先从操作系统线程开始说起,让我们…

标准IO与系统IO

C平台的标准IO(可跨系统)Linux 系统IO (Linux系统平台才能使用)Linux系统的IO(输入输出)特点主要包括以下几个方面 1)文件视为一切:在Linux中,几乎所有的设备和资源都被视为文件。这意味着无论是硬件设备(如磁盘、网络接口)还是系统资源(如进程、内存),它们都可以通…

比snipaste更好用的截图工具 pixpin

pixpin官网功能介绍与 snipaste 的功能相似,比snipaste好用作者:mohistH 出处:https://www.cnblogs.com/pandamohist/ 本文版权归作者和博客园共有,谢绝一切形式的转载,否则将追究法律责任。

HT-014 Div3 扫雷 题解 [ 绿 ] [ 二维差分 ]

分析 观察到是曼哈顿距离 \(\le r\) 的范围可以扫到,联想到如下图形:左边是 \(r=1\) 可以扫到的范围,右边是 \(r=2\) 可以扫到的范围。 于是,我们只要对这样的图形在 \(1000*1000\) 的格子里差分一下就好了 。 但这样的复杂度是 \(O(nm)\) 的,会死的很惨。 优化 不难发现这…

[VSCode] Add all missing import

https://github.com/microsoft/vscode-docs/blob/vnext/release-notes/v1_46.md#add-all-missing-imports-source-action

秒杀圣经:10Wqps高并发秒杀,16大架构杀招,帮你秒变架构师

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…