【图论】2-SAT

参考资料:2-SAT学习笔记

什么是2-SAT问题呢?

(¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c),给出一个类似于这样的式子,让你找出满足条件的一个解,这样的问题就是SAT问题,因为每一个括号内都有三个被限制的变量,所以这叫做3-SAT问题(是因为括号内的变量数有3个才叫3-SAT,不是因为abc才叫3-SAT)

所以2-SAT也很好理解,(¬a∨b)∧(a∨c)∧(¬c∨¬b)就叫做 2-SAT 问题

可以证明 3-SAT 及以上的问题只能用暴力枚举解决(我也不知道怎么证明),所以我们只讨论2-SAT问题

理论知识

前置知识,你需要学会【图论】有向图的强连通分量

我们将 a V b 理解成:选择了a就不能选b,选择了b就不能选aa b必须要选择一个

那我们就可以得到这样的关系:选择了a就要选择¬b, 选择了b就要选择¬a,反之也成立

根据这个关系建图,我们可以得到

在这里插入图片描述

我们可以看出,所有在一个强连通分量里的元素是等价的

因此,建好图之后,只要出现 x¬x 在一个强连通分量里,就说明它们等价,也就出现了矛盾,无解

接下来是一道洛谷的模板题

例题

P4782 【模板】2-SAT

题目链接

题目描述

n n n 个布尔变量 x 1 x_1 x1 ∼ \sim x n x_n xn,另有 m m m 个需要满足的条件,每个条件的形式都是 「 x i x_i xitrue / false x j x_j xjtrue / false」。比如 「 x 1 x_1 x1 为真或 x 3 x_3 x3 为假」、「 x 7 x_7 x7 为假或 x 2 x_2 x2 为假」。

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

输入格式

第一行两个整数 n n n m m m,意义如题面所述。

接下来 m m m 行每行 4 4 4 个整数 i i i, a a a, j j j, b b b,表示 「 x i x_i xi a a a x j x_j xj b b b」( a , b ∈ { 0 , 1 } a, b\in \{0,1\} a,b{0,1})

输出格式

如无解,输出 IMPOSSIBLE;否则输出 POSSIBLE

下一行 n n n 个整数 x 1 ∼ x n x_1\sim x_n x1xn x i ∈ { 0 , 1 } x_i\in\{0,1\} xi{0,1}),表示构造出的解。

样例输入 #1

3 1
1 1 3 0

样例输出 #1

POSSIBLE
0 0 0

提示

1 ≤ n , m ≤ 1 0 6 1\leq n, m\leq 10^6 1n,m106 , 前 3 3 3 个点卡小错误,后面 5 5 5 个点卡效率。

由于数据随机生成,可能会含有( 10 0 10 0)之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里。

code

#include <bits/stdc++.h>

using namespace std;

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int n, m;
	cin >> n >> m;

	vector<vector<int>> g(2 * n + 1);
	for (int i = 1; i <= m; i ++ )
	{
		int a, va, b, vb;
		cin >> a >> va >> b >> vb;
		g[a + !va * n].push_back(b + vb * n);
		g[b + !vb * n].push_back(a + va * n);
		// 下面四行和上两行等价
		// if (va && vb) g[a].push_back(b + n), g[b].push_back(a + n);
		// else if (va && !vb) g[a].push_back(b), g[b + n].push_back(a + n);
		// else if (!va && vb) g[a + n].push_back(b + n), g[b].push_back(a);
		// else if (!va && !vb) g[a + n].push_back(b), g[b + n].push_back(a);
	}

	vector<int> dfn(2 * n + 1), low(2 * n + 1), id(2 * n + 1);
	vector<bool> in_stk(2 * n + 1);
	int timestamp = 0, scc_cnt = 0;
	stack<int> stk;

	function<void(int)> tarjan = [&](int u)
	{
    	dfn[u] = low[u] = ++ timestamp; // 先将dfn和low都初始化为时间戳
    	stk.push(u), in_stk[u] = true; // u加入栈中

    	for (int i = 0; i < g[u].size(); i ++ )
    	{
    	    int j = g[u][i]; // 取出u的所有邻点j
    	    if (!dfn[j]) // 如果j还没被遍历
    	    {
    	        tarjan(j);
    	        low[u] = min(low[u], low[j]); // 用low[j]更新low[u]
    	    }
    	    else if (in_stk[j]) low[u] = min(low[u], dfn[j]); // 如果j已入栈 则用dfn[j]更新low[u]
    	}

    	if (dfn[u] == low[u]) // 如果该点是所在强连通分量的最高点
    	{
    	    ++ scc_cnt; // 强连通分量数量加一
    	    int y;
    	    do {
    	        y = stk.top(); // 取出栈顶元素
    	        stk.pop();
    	        in_stk[y] = false;

    	        id[y] = scc_cnt; // 标记每个点所在的连通分量编号
    	    } while (y != u); // 直到取到此连通分量的最高点为止
    	}
	};

	for (int i = 1; i <= 2 * n; i ++ )
		if (!dfn[i]) tarjan(i);

	for (int i = 1; i <= n; i ++ )
		if (id[i] == id[i + n])
		{
			cout << "IMPOSSIBLE\n";
			return 0;
		}
	
	cout << "POSSIBLE\n";
	for (int i = 1; i <= n; i ++ )
		if (id[i] > id[i + n]) cout << 1 << ' ';
		else cout << 0 << ' ';
	cout << '\n';
}
  • 38
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Texcavator

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值