对于Tarjan强连通分量算法的理解
今天比较无聊开始复习图论,对于我这么一个不怎么爱写板子的蒟蒻来说,终于打算回(yu)顾(xi)一下Tarjan的强连通算法
首先给出Tarjan算法的原理:
原理
Tarjan的算法主要基于DFS树,基本和求点双,边双什么的差不多。对于一棵DFS树,我们肯定是按照DFS序来遍历它的(因为它叫DFS树)。这时,我们记录一个叫做lowlink的数组,对于lowlonk[x]来说,它记录的是在这棵DFS树上编号为x的节点所能够达到的最早的节点。
什么叫最早的节点呢?这要用时间截来予以衡量。时间截就是记录访问到某一节点x的时间(或者说x是DFS序中的第几个),在这个算法中我们记录刚刚访问到它的时间,用一个数组pre[]来予以记录,但不记录离开它的时间(这两种的区别在于刚刚访问到的时候我们还没访问它的后继节点,即还未进行下一步DFS,而离开它时,我们已经访问完了这个节点的后继节点们。这两种时间我们常常用于树链剖分以及其中的维护子树信息的操作,更多内容可以参阅树链剖分的相关资料)。
然后,我们每次用x的后继节点以及DFS树上的反向边所指向的节点(反向边,即非树边,原图中存在但由于DFS序的原因未出现于DFS树上的边)的lowlink来更新x的lowlink,在这里我们应该使用min()函数,因为lowlink越小表示访问的时间越早。
最后,我们只要考虑这样的节点x,它满足pre[x]==lowlink[x],这说明它最早到达的节点是它自己,那么它与它的后继们就在同一个强连通分量中(其实这里表示并不严密,是指x节点的特定的后继,如果它的某些后继已经被确定属于某个强连通分量了,那么这个后继就不算)。这样我们就求出了所有的强连通分量。算法中还有很多的细节,下文将予以说明
先贴上本人丑陋的代码(代码中low==lowlink):
Tarjan算法模板
vector<int> geo[maxn];
stack<int> scc;
bool vis[maxn];
int sccno[maxn],low[maxn],pre[maxn];
int time=0,cnt=0;
int Tarjan(int x){
pre[x]=low[x]=++time;
scc.push(x);
vis[x]=true;
for(int i=0;i<geo[x].size();i++){
int op=geo[x][i];
if(!vis[op]){
low[x]=min(low[x],Tarjan(op));
}else if(!sccno[op]){
low[x]=min(low[x],pre[op]);
}
}
if(low[x]==pre[x]){
int op=scc.top();
scc.pop();
++cnt;
while(op!=x){
sccno[op]=cnt;
op=scc.top();
scc.pop();
}
sccno[x]=cnt;
}
return low[x];
}
细节
忍不住吐个槽,markdown的有序列表功能真是烂到不行
我们要在访问到一个新节点的时候立刻初始化它的lowlink值为pre值,同时立刻将这个点压入栈中,并记录已经访问(在这上面被坑的应该不计其数)
判断时,我们要使用pre值来进行与lowlink值的比较,而不能使用time这是因为time已经游走各地发生了巨大改变,早已不是原来的他了
在上述代码第15行我们使用了反向边另一端节点的pre值来更新,那么我们为什么不使用它的lowlink值呢?其实都可以,反正返回到那个节点的时候该求出来的都已经求出来了,但是pre值它更加稳定,它一直都是那个值
另外我们为什么要判断反向边的另一端的节点是否已经被标号了呢(代码第14行)?这是因为如果它被标号了,那说明那个节点不和当前的这个节点在同一个正在研究的子树中(相对于DFS树来说),那个分支在之前就已经被DFS到了。哪为什么它没标号就是可以的呢?这是因为属于别的分支中的点,如果已经被研究到,那么一定就已经被标上号了,哪怕SCC中只有它一个点
总结
代码不长,理解起来并不困难,虽然也不是经常使用(相对于DP,数学什么的来说),但是会了总比不会强
例题
由于我可能会写其他的文章,所以这里先留坑待补,以后链上链接。其实这些题也都不难。。。要不先列举几个
间谍网络
题目描述
由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。
我们的反间谍机关提供了一份资料,色括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过3000),每个间谍分别用1到3000的整数来标识。
请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。
输入输出格式
输入格式:
第一行只有一个整数n。
第二行是整数p。表示愿意被收买的人数,1≤p≤n。
接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。
紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。
输出格式:
如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。
输入输出样例
输入样例
【样例1】
3
2
1 10
2 100
2
1 3
2 3
【样例2】
4
2
1 100
4 200
2
1 2
3 4
输出样例:
【样例1】
YES
110
【样例2】
NO
3
这是一道洛谷的题。。。
[HAOI2006]受欢迎的牛
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶
牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜
欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你
算出有多少头奶牛可以当明星。
输入格式:
第一行:两个用空格分开的整数:N和M
第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B
输出格式:
第一行:单独一个整数,表示明星奶牛的数量
输入输出样例
输入样例#1:
3 3
1 2
2 1
2 3
输出样例#1:
1
说明
只有 3 号奶牛可以做明星
数据范围
10%的数据N<=20, M<=50
30%的数据N<=1000,M<=20000
70%的数据N<=5000,M<=50000
100%的数据N<=10000,M<=50000
这是一道洛谷上的题。。。