前置知识: 强连通分量
什么是 2 − S A T 2-SAT 2−SAT ?
2 − S A T 2-SAT 2−SAT 算法一般是指有 n n n 个关系集合,集合中有两个元素,一般必须选其中一个。此外还有若干个限制条件,求可行的方案。
理论思路
以 【模板】2-SAT 问题 为例。
一个关系集合之中的两个元素,我们用 u u u 和 v v v 来表示。
在这 n n n 个关系集合之中,有 n n n 组相对关系,一个为 t r u e true true,一个为 f a l s e false false。
本题中,相对关系指 x x x 为真或 x x x 为假。
其中,如果我们选了一个元素 u u u,另一个与 u u u 相对的元素 u ′ u' u′ 我们就不能再选,因为相互冲突。 v v v 也是同理。
因为每个元素都在属于它的含有两个元素的集合之中,且必须选择一个,所以如果没有选 u u u 就必须选 v v v。相反,如果我们选了 u u u,就不必再选 v v v。
根据刚刚推导出来的的理论,我们可以得出,如果选择了与 u u u 相对的元素 u ′ u' u′,也就必须选择属于 u u u 关系集合中的另一个元素 v v v;如果选择了与 v v v 相对的元素 v ′ v' v′,也就必须选择属于 v v v 关系集合中的另一个元素 u u u。
那么, 2 − S A T 2-SAT 2−SAT 问题的建立规则也就是:选择任意一个元素的相对元素,就必须选择此元素所在集合的另外一个元素。
代码思路
那如何在代码中实现呢?
根据我们上面的结论,可以发现,他们都是属于一种依赖关系。那我们可不可以建成一个图,用来表示如果选择 u u u,则必须选 v v v 呢?
答案是可以的
我们将集合中每一个元素的相对元素向集合中另一个元素连一条有向边,表示选了相对元素就必须选另一个元素。
那我们又如何判断有没有解呢?
我们知道,在任何一个强连通分量当中,无论是那个点,都可以到达此强连通分量当中的任意一点。
所以根据这个性质,如果 u u u 与 u ′ u' u′ 处于同一个强连通分量当中,那 u u u 就依赖于 u ′ u' u′ 或者是 u ′ u' u′ 就依赖于 u u u。显然,这与 2 − S A T 2-SAT 2−SAT 的定理互相冲突,也就是说明不可能得出一种合法的依赖关系,也就是说此情况无解。
那么又如何再查找到此情况的一组解?
在拓扑序中,拓扑序越大遍历的顺序也就越靠后。在强连通分量中, d f s dfs dfs 序越小,也就表示先被缩点,而拓扑序越大,也是先被缩点。所以 d f s dfs dfs 序就是拓扑序的反序。
如果 d f s dfs dfs 序越小,拓扑序越大,可能性也就越大。
因为强连通分量是一个深搜的过程,在查找的时候,自然也就是拓扑序越大的自然要在后遍历到的强连通分量的缩点缩得更快一些,也就是优先被处理了出来,所以自然可能性更要大一些。
贴代码
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to,next;
}ed[10000001];
int he[10000001],tot;
int dfn[10000001],idx;
int low[10000001];
bool vis[10000001];
int color[10000001],kind;
stack<int>s;
int n,m;
void insert(int u,int v)
{
tot++;
ed[tot].to=v;
ed[tot].next=he[u];
he[u]=tot;
}
void tarjan(int u)
{
low[u]=dfn[u]=++idx;
s.push(u);vis[u]=1;
for(int i=he[u];i!=-1;i=ed[i].next)
{
int v=ed[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int x;
kind++;
do
{
x=s.top();s.pop();
vis[x]=0;
color[x]=kind;
}while(x!=u);
}
}
int main()
{
memset(he,-1,sizeof(he));
scanf("%d %d",&n,&m);
while(m--)
{
int u,x,v,y;
scanf("%d %d %d %d",&u,&x,&v,&y);
if(x&&y)
{
insert(u+n,v);
insert(v+n,u);
}
if(x&&!y)
{
insert(u+n,v+n);
insert(v,u);
}
if(!x&&y)
{
insert(u,v);
insert(v+n,u+n);
}
if(!x&&!y)
{
insert(v,u+n);
insert(u,v+n);
}
}
for(int i=1;i<=n*2;i++)
if(!dfn[i]) tarjan(i);
for(int u=1;u<=n;u++)
if(color[u]==color[u+n])
{puts("IMPOSSIBLE");return 0;}
puts("POSSIBLE");
for(int i=1;i<=n;i++)
{
if(color[i]<color[i+n]) printf("1 ");
else printf("0 ");
}
return 0;
}