码迷,mamicode.com
首页 > 其他好文 > 详细

强连通分量 与 2-SAT

时间:2021-02-01 12:39:26      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:说明   没有   一个   利用   通过   路径   深度优先   别人   必须   

近期一直在刷这方面的题 因为没法学新知识 但又想写点什么 就水篇博文吧

引理

简单来说,在一个有向图中,若所有点之间两两互相直接可达,则将这个图成为强连通分量

强连通分量可以是某个有向图中的子图

求强连通分量可以使用 Tarjan,Kosaraju 或者 Garbow 算法

个人感觉 Tarjan算法 最实用,而且后两种算法并不是很熟练,因此在这里只讲 Tarjan算法

Tarjan算法

发明者 Robert E.Tarjan 罗伯特·塔扬,美国计算机科学家

塔老爷子发明过很多算法,而且大多是以他的名字命名的,所以 Tarjan算法 也分很多种,这里只说如何求强连通分量

首先需要知道什么是 DFS序

一个结点 \(x\) 的 DFS序 是指深度优先搜索遍历时改结点被搜索的次序,简记为 \(dfn[x]\)

然后,再维护另一个变量 \(low[x]\)

\(low[x]\) 表示以下节点的 DFS序 的最小值:以 \(x\) 为根的子树中的结点 和 从该子树通过一条不在搜索树上的边能到达的结点

根据 DFS 的遍历原理可以发现

  • 一个结点的子树内结点的 DFS序 都大于该结点的 DFS序

  • 从根开始的一条路径上的 DFS序 严格递增,low值 严格非降

知道了这些,再来看 Tarjan算法 求强连通分量的具体内容

我们一般只对还没有确定其 DFS序 的节点进行 Tarjan 操作,操作主要包括两个部分

第一部分

以 DFS 的形式,处理出当前点 \(x\)\(dfn[x]\)\(low[x]\)

对当前点打一个标记表示已经遍历过,在之后的 DFS 中根据是否遍历过来进行不同处理,具体方式如下:

设当前枚举点为 \(fr\)\(fr\) 连出去的点记为 \(to\)

  1. \(to\) 未被访问:继续对 \(to\) 进行深度搜索。在回溯过程中,用 \(low[to]\) 更新 \(low[fr]\)。因为存在从 \(fr\)\(to\) 的直接路径,所以 \(to\) 能够回溯到的已经在栈中的结点, \(fr\) 也一定能够回溯到。
  2. \(to\) 被访问过,已经在栈中:即已经被访问过,根据 low值 的定义(能够回溯到的最早的已经在栈中的结点),则用 \(dfn[to]\) 更新 \(low[fr]\)
  3. \(to\) 被访问过,已不在在栈中:说明 \(to\) 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

这一部分代码实现如下:

low[fr]=dfn[fr]=++cnt;vis[fr]=1;
for(int i=head[fr];i;i=e[i].nxt){
    int to=e[i].to;
    if(!dfn[to]) tarjan(to),low[fr]=min(low[fr],low[to]);
    else if(vis[to]) low[fr]=min(low[fr],dfn[to]);
}

第二部分

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 \(dfn[x]=low[x]\)

该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFS序 和 low值 最小,不会被该连通分量中的其他结点所影响

我们可以维护一个栈,存储所有枚举到的点

因此,在回溯的过程中,判定 \(dfn[x]=low[x]\) 的条件是否成立,如果成立,则从栈中取出一个点,处理它所在的强连通分量的编号以及大小,也可以处理其他的一些操作,这样直到把所有点处理完为止

这一部分的代码实现如下:

zhan[++top]=u;
if(dfn[u]==low[u]){
	++siz[++t];
        int pre=zhan[top--];
        vis[pre]=0;num[pre]=t;
	while(pre!=u){
	    ++siz[t];pre=zhan[top--]; 
	    vis[pre]=0;num[pre]=t;
	}
}

至此,便可以处理出一个点所在的强连通分量,时间复杂度为 \(O(n+m)\)

2-SAT

SAT 是适定性(Satisfiability)问题的简称。一般形式为 k-适定性问题,简称 k-SAT。而当 \(k>2\) 时该问题为 NP 完全的。所以我们只研究 \(k=2\) 的情况。 —— OI Wiki

个人感觉,就是一个实际应用类的知识吧

就是指定 \(n\) 个集合,每个集合包含两个元素,给出若干个限制条件,每个条件规定不同集合中的某两个元素不能同时出现,最后问在这些条件下能否选出 \(n\) 个不在同一集合中的元素

这个问题一般用 Tarjan算法 来求解,也可以使用爆搜,可以参考OI Wiki上的说明,这里就只讲用 Tarjan 实现

但这种问题的实现主要不是难在 Tarjan 怎么写,而是难在图怎么建

假设这里有两个集合 \(A=\{x_1,y_1\}\)\(B=\{x_2,y_2\}\),规定 \(x_1\)\(y_2\) 不可同时出现,那我们就建两条有向边 \((x_1,y_1)\)\((y_2,x_2)\),表示选了 \(x_1\) 必须选 \(y_1\),,选了 \(y_2\) 必须选 \(x_2\)

这样建完边之后只需要跑一边 Tarjan 判断有无解,若有解就把几个不矛盾的强连通分量拼起来就好了

这里注意,因为跑 Tarjan 用了栈,根据拓扑序的定义和栈的原理,可以得到 跑出来的强连通分量编号是反拓扑序 这一结论

我们就可以利用这一结论,在输出方案时倒序得到拓扑序,然后确定变量取值即可

时间复杂度同上为 \(O(n+m)\)

例题

[APIO2009]抢掠计划
[USACO5.3]校园网Network of Schools
[ZJOI2007]最大半连通子图
[POI2001]和平委员会

写在后面

这种知识加起来已经学过好几遍了,但是写起来还是很累,总是怕自己说不明白
有一说一,我是真不理解有人只放个题目链接然后放个代码是怎么想的,连思路都不提,是给别人写 std 还是只为了告诉别人自己做对了
比我还水

强连通分量 与 2-SAT

标签:说明   没有   一个   利用   通过   路径   深度优先   别人   必须   

原文地址:https://www.cnblogs.com/KnightL/p/14351657.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!