2-SAT 问题
2-SAT 问题
1. 模型
有
个布尔类型的变量 n ,有 x1,x2,…,xn 条限制形如 m . xi [or/and] xj=[1/0]
求一组符合要求的解。
核心问题只需要考虑有没有解。
对于每个变量都只有两种取值:
一般地,对
本质上是找到确定的变量间关系并判断是否有关系冲突。
连上边后,通过 Tarjan 找到所有强联通分量。同一强连通分量内的变量值一定是相等的,所以只需判断表示
在构造解时,对每个节点
注意到在 Tarjan 得到的搜索树上,拓扑序越大的点越远离树根,强联通分量编号也越小。所以直接选择表示两种取值的节点中强联通分量的编号较小的点即可。
2. 例题
2.1. 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 = 0
g[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 = 1
g[b].push_back(a); // b = 0 -> a = 0
}
else if(x == 1 && y == 0)
{
g[a].push_back(b); // a = 0 -> b = 0
g[b + n].push_back(a + n); // a = 1 -> b = 1
}
else
{
g[a].push_back(b + n); // a = 0 -> b = 1
g[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;
}
2.2. P4171 [JSOI2010] 满汉全席
和模板题一样,不过要用
初始化时要注意有
#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;
}
2.3. P6378 [PA2010] Riddle
- 对于“每条边至少有一个端点是关键点”的限制,按照“
” 的限制来连边即可。a=1 or b=1 - 对于“每个部分恰有几个关键点”的限制,直接连边会导致边数过多,达到
级别,所以考虑对建图进行一些优化。O(n2)
第一条限制的本质,是在每一个部分中,把每一个点的 “真状态” 连向其余所有点的 “假状态”。并且只要保证连通即可。考虑进行前后缀优化建图,将连向除了自身的另一个状态以外的所有点,转化为连向这个点之前的前缀以及这个点之后的后缀。
此时,这个新图的性质已经和原图完全等价了。
直接跑 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 = 1
n + 1 ~ 2n: x = 0
2n + 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;
}
2.4. P3209 [HNOI2010] 平面图判定
首先,根据平面图的性质,若 NO
。
将哈密顿回路画成一个圆,那么原图上不在哈密顿回路上的边可以看作是该圆的弦。
考虑圆中两条相交的弦
设
具体连边有
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效