2402. 2-SAT 问题

题目链接

2402. 2-SAT 问题

给定 \(n\) 个还未赋值的布尔变量 \(x_1 \sim x_n\)

现在有 \(m\) 个条件,每个条件的形式为 “\(x_i\)\(0/1\) \(x_j\)\(0/1\) 至少有一项成立”,例如 “\(x_1\)\(1\)\(x_3\)\(0\)”、“\(x_8\)\(0\)\(x_4\)\(0\)” 等。

现在,请你对这 \(n\) 个布尔变量进行赋值(\(0\)\(1\)),使得所有 \(m\) 个条件能够成立。

输入格式

第一行包含两个整数 \(n,m\)

接下来 \(m\) 行,每行包含四个整数 \(i,a,j,b\),用来描述一个条件,表示 “\(x_i\)\(a\)\(x_j\)\(b\)”。

输出格式

如果问题有解,则第一行输出 POSSIBLE,第二行输出 \(n\) 个整数表示赋值后的 \(n\) 个变量 \(x_1 \sim x_n\) 的值(\(0\)\(1\)),整数之间用单个空格隔开。

如果问题无解,则输出一行 IMPOSSIBLE 即可。

如果答案不唯一,则输出任意一种正确答案即可。

数据范围

\(1 \le n,m \le 10^6\),
\(1 \le i,j \le n\),
\(0 \le a,b \le 1\)

输入样例:

3 2
1 1 3 1
2 0 3 0

输出样例:

POSSIBLE
1 1 0

解题思路

2-SAT

2-SAT 通常解决这样一类问题:给出若干个这样的命题 \(x_1\sim x_n\),给出一些条件 \(x_i\cup x_j\),即 \(x_i\)\(x_j\) 为真,给所有的命题判断真假

首先,有如下等价关系:\(x\cup y\Leftrightarrow \neg x\rightarrow y\Leftrightarrow \neg y\rightarrow x\),则将 \(x\)\(\neg x\) 看成两个不同的节点,将 \(\neg x\)\(y\) 连边,\(\neg y\)\(x\) 连边,即如果 \(\neg x\) 为真的话 \(y\) 也必须为真,\(\neg y\) 为真的话 \(x\) 也必须为真,则这样的关系依赖图显然会形成一个有向图,将该有向图缩点后,在一个强连通分量内判断是否存在这样一条 \(x\rightarrow \neg x\),即如果 \(x\) 为真的话 \(\neg x\) 也必然为真,显然矛盾,即整个命题无解,否则\(\color{red}{是否一定有解?}\)不妨构造这样的解:缩点后,对于 \(x\)\(\neg x\) 来说,如果 \(x\) 的拓扑序要靠后,则置 \(x\) 为真,否则置为假。\(\color{red}{为什么这样是正确的?}\)首先一个强连通分量一定与另外某个强连通分量是对偶的关系,即如果一个强连通分量某含有这样的命题:\(x,\neg y,z\),则一定存在另外一个强连通分量含这样的命题:\(\neg x,y,\neg z\),因为如果存在这样一条路径:\(x\rightarrow \neg y\rightarrow z\),则一定存在这样一条路径:\(\neg x\rightarrow y\rightarrow \neg z\),要使上面的构造方案为假,即证明某个命题:\(x\cup y\),其中 \(x\) 为假且 \(y\),反证:\(x\) 为假时 \(y\) 必然为真,因为 \(x\) 为假,即 \(\neg x\) 的拓扑序靠后,而有这样的连边 \(\neg x\rightarrow y\),即 \(y\) 的拓扑不比 \(\neg x\) 靠前,同时也存在这样的连边 \(\neg y\rightarrow x\),即 \(\neg y\) 不比 \(x\) 靠前,则 \(y\) 的拓扑序要比 \(\neg y\) 靠后,即 \(y\) 会置为真,得证

  • 时间复杂度:\(O(n+m)\)

代码

// Problem: 2-SAT 问题
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2404/
// Memory Limit: 512 MB
// Time Limit: 5000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=2e6+5;
int n,m;
int h[N],ne[N],e[N],idx;
int dfn[N],low[N],timestamp,scc_cnt,id[N],stk[N],top;
bool in_stk[N];
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++timestamp;
	stk[++top]=x,in_stk[x]=true;
	for(int i=h[x];~i;i=ne[i])
	{
		int y=e[i];
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(in_stk[y])
			low[x]=min(low[x],dfn[y]);
	}
	if(low[x]==dfn[x])
	{
		int y;
		scc_cnt++;
		do
		{
			y=stk[top--];
			in_stk[y]=false;
			id[y]=scc_cnt;
		}while(y!=x);
	}
}
int main()
{
	memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    while(m--)
    {
    	int i,a,j,b;
    	scanf("%d%d%d%d",&i,&a,&j,&b);
    	i--,j--;
    	add(2*i+!a,2*j+b);
    	add(2*j+!b,2*i+a);
    }
    for(int i=0;i<2*n;i++)
    	if(!dfn[i])tarjan(i);
    for(int i=0;i<n;i++)
    	if(id[i<<1]==id[i<<1|1])
    	{
    		puts("IMPOSSIBLE");
    		return 0;
    	}
    puts("POSSIBLE");
    for(int i=0;i<n;i++)
    	if(id[i<<1]<id[i<<1|1])printf("%d ",0);
    	else
    		printf("%d ",1);
    return 0;
}
posted @ 2022-12-03 22:41  zyy2001  阅读(19)  评论(0编辑  收藏  举报