「Note」图论方向 - 图论进阶

1. 2-SAT

1.1. 介绍

对于一些节点,每个节点存在两个状态(非 \(0\)\(1\)),我们给出一些如下类型的限制条件:

  • 节点 \(i\) 状态为 \(1/0\)
  • 若节点 \(i\) 状态为 \(1/0\),那么节点 \(j\) 状态为 \(1/0\)
  • 节点 \(i,j\ (i\not=j)\) 至少有一个为 \(1/0\)

2-SAT 算法用于解决类似的问题,每个节点最多只能有两种状态。

我们用有向图表示节点状态之间的递推关系,我们设 \(p_i\) 表示节点 \(i\) 状态为真,我们将上述限制条件写为表达式并写为“若 \(p\) ,则 \(q\)”的形式,便于用有向图表示它们的关系:

  • \(p_i\):若 \(\lnot p_i\),则 \(p_i\)
  • \(\lnot p_i\):若 \(p_i\),则 \(\lnot p_i\)
  • \(p_i\lor p_j\):若 \(\lnot p_i\)\(p_j\);若 \(\lnot p_j\)\(p_i\)
  • \(\lnot p_i\lor\lnot p_j\):若 \(p_i\)\(\lnot p_j\);若 \(p_j\)\(\lnot p_i\)
  • \(\lnot p_i\lor p_j\):若 \(p_i\)\(p_j\);若 \(\lnot p_j\)\(\lnot p_i\)

若有 \(p\Rightarrow q\),并且 \(p,q\) 相互矛盾,则 \(p\) 一定为假。
更进一步地,若有 \(p\Leftrightarrow q\),并且 \(p,q\) 相互矛盾,则此限制条件下,无解。

考虑无解的情况在有向图中的表现形式,即两个矛盾状态在图中处于同一个强连通分量中,我们考虑用 Tarjan 算法进行缩点。

对于一种可行方案则在 \(p_i,\lnot p_i\) 两个状态点中选择拓扑序更小的即可,用 Tarjan 后可省去拓扑排序过程,详见我其他博客。

1.2. 例题

\(\color{royalblue}{P4782}\)

板子题。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
//IO
inline int rd()
{
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
//--------------------//
const int N=2e6+5;

int n,m;
//--------------------//
struct Edge
{
	int to,nex;
}edge[N];
int tot,head[N];
void add(int from,int to)
{
	edge[++tot]={to,head[from]};
	head[from]=tot;
	return;
}

int dcnt,dfn[N],low[N];
int sccnt,scc[N];
int stop,sta[N];
bool stv[N];

void Tarjan(int now)
{
	low[now]=dfn[now]=++dcnt;
	sta[++stop]=now,stv[now]=true;
	for(int to,i=head[now];i;i=edge[i].nex)
	{
		to=edge[i].to;
		if(!dfn[to])
		{
			Tarjan(to);
			low[now]=min(low[now],low[to]);
		}
		else if(stv[to])
			low[now]=min(low[now],dfn[to]);
	}
	if(low[now]==dfn[now])
	{
		scc[now]=++sccnt;
		stv[now]=false;
		while(sta[stop]!=now)
		{
			scc[sta[stop]]=sccnt;
			stv[sta[stop]]=false;
			stop--;
		}
		stop--;
	}
	return;
}
//--------------------//
int main()
{
	scanf("%d%d",&n,&m);
	for(int a,x,b,y,i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&a,&x,&b,&y);
		add(a+(!x)*n,b+y*n);
		add(b+(!y)*n,a+x*n);
	}
	for(int i=1;i<=2*n;i++)
		if(!dfn[i])
			Tarjan(i);
	for(int i=1;i<=n;i++)
		if(scc[i]==scc[i+n])
		{
			printf("IMPOSSIBLE\n");
			return 0;
		}
	printf("POSSIBLE\n");
	for(int i=1;i<=n;i++)
		if(scc[i]<scc[i+n])
			printf("0 ");
		else
			printf("1 ");
    return 0;
}
posted @ 2023-08-21 13:09  Eon_Sky  阅读(13)  评论(0编辑  收藏  举报