「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;
}