n-SAT 是给定多个条件,问是否有一个赋值方式使所有条件得到满足。
每个条件都有n个变量, 而2-SAT 是存在时间复杂度为(O(nm)或O(n+m))的做法((m)是条件数)的算法的问题。
2-SAT往往是判断是否有方案使条件得到满足的一个算法。
建图:
求解2-SAT问题,需要转移到图上,对于每个变量都定义两个点分别表示true和false,用点a到点b连边表示a要选b也一要选。因此a的拓扑序一定比b要大,因为b给了a一个限制。
判断:
2-SAT有多种实现方式,用的最多的就是用tarjan来判断可行性。
tarjan来判断可行性的原理是,通过缩点之后,如果每个变量的true和false不属于一个连通分量,就说明可行。
输出方案:
因为缩点之后是深搜使得tarjan缩完点之后的编号是是倒着的拓扑序,而我们又想找到对后来限制少的点,即拓扑序大的点,所以我们找tarjan缩完点之后的编号小的点。
#include <bits/stdc++.h>
#include <stack>
#define N 2001010
using namespace std;
struct edge {
int to, nex;
}e[N * 2];
int n, m, tot, color, cnt, flag[N], lin[N], dfn[N], low[N], belong[N], vis[N];
stack <int> s;
inline void add(int f, int t)
{
e[++cnt].to = t;
e[cnt].nex = lin[f];
lin[f] = cnt;
}
void tarjan(int now)
{
dfn[now] = low[now] = ++tot;
vis[now] = 1, s.push(now);
for (int i = lin[now]; i; i = e[i].nex)
{
int to = e[i].to;
if (!dfn[to])
tarjan(to), low[now] = min(low[now], low[to]);
else if (vis[to])
low[now] = min(low[now], dfn[to]);
}
if (dfn[now] == low[now])
{
++color;
while (!s.empty())
{
int v = s.top(); s.pop();
belong[v] = color, vis[v] = 0;
if (v == now) break;
}
}
}
int main()
{
scanf("%d%d", &n, &m);
while (m--)
{
int i, a, j, b;
scanf("%d%d%d%d", &i, &a, &j, &b);
add(j + (b ^ 1) * n, i + a * n);
add(i + (a ^ 1) * n, j + b * n);
}//就是xi为a时, xj一定 不为b,反过来xj一定不为b时,xi并不满足一定为a,因此要从j+(b^1)*n连向i+a*n
for (int i = 1; i <= n * 2; i++)
if (!dfn[i])
tarjan(i);
for (int i = 1; i <= n; i++)
if (belong[i] == belong[i + n])
printf("IMPOSSIBLE
"), exit(0);
printf("POSSIBLE
"); //最后我们要选对后来限制小的点,所以就是要选拓扑序大的,也就是belong小的
for (int i = 1; i <= n; i++)
{
if (belong[i] < belong[i + n])//无相等情况
printf("1 ");
else
printf("0 ");
}
return 0;
}