(这是一篇我咕咕咕了很久的文章,但是我还是把它翻出来写)
1 题面
1-1 题目链接
1-2 题目大意
给你一个有n个点,n条边的图,求每个点出发的最大环的大小。
2思路
首先做一遍tarjan缩点,组成一个强连通分量的点出发所形成的最大环就是这个强连通分量的大小,不在环中的就要去dfs它到最近环的距离再加上搜索到的那个环的大小(需要特判大小为1的环)。
3实现
3-1 tarjan缩点
tarjan缩点就是将每一个强连通分量染上同样的颜色,那么我们就可以把同样颜色的一个强连通分量看作一个独立的个体(也就是一个点)。
我们用edges(链式前向星)来记录边(附带一个head)。
用dfn[i]来记录i号点被访问到的顺序(时间戳)。
用low[i]数组记录i号点可以访问到的时间戳最小的节点。
belong[i]代表i号点被染成了什么颜色(属于那个强连通分量)。
s是一个栈,用来储存访问到的点,方便染色。
细节见代码:
stack <int> s;
void tarjan(int u){
indx++;
dfn[u]=indx;//将u节点打上时间戳
low[u]=indx;
s.push(u);
for(int i=head[u];i;i=edges[i].nxt){
int v=edges[i].to;//遍历每一个从u出发的边
if(!dfn[v]){//如果v没有被访问过就搜索v
tarjan(v);
low[u]=min(low[u],low[v]);//更新u的low(v能访问到的点,u也能访问到)
}
else if(!belong[v]){//如果被访问过了,可是还没有属于任何一个强连通分量
low[u]=min(dfn[v],low[u]);//更新u的low(u能到v,那么low[u]应该更新成low[u]和v的时间戳的最小值
}
}
if(dfn[u]==low[u]){//如果顺序和low相等,那么u不可能访问到别的分量中,开始染色
col++;
belong[u]=col;//先把u染上
while(s.top()!=u){//依次出栈
belong[s.top()]=col;//染色
s.pop();
}
s.pop();//不要忘记把u弹出去
}
}
3-2处理出每个强连通分量的大小
这一步工作挺简单的,siz[i]数组存第i个强连通分量的大小。
len就是答案数组了。
for(int i=1;i<=n;i++){
siz[belong[i]]++;
}
for(int i=1;i<=n;i++){
if(siz[belong[i]]!=1){//如果大小不是1(即不为单点)
len[i]=siz[belong[i]];
}
}
3-3处理单点
遍历每个点,如果没有值就要特判。
for(int i=1;i<=n;i++){
if(!len[i]){
dfs(i,nex[i],1);
}
}
dfs函数实现:
void dfs(int start,int now,int step){
if(len[now]){//如果这个now点处于一个强连通分量中
len[start]=len[now]+step;//答案=len[now]+已经搜索过的路径长
return;
}
else{//如果不在
dfs(start,nex[now],step+1);
}
}
3-4完整代码参考
#include <cstdio>
#include <iostream>
#include <stack>
#define maxn 100001
using namespace std;
int n,cnt,col,indx,dfn[maxn],nex[maxn],low[maxn],belong[maxn],head[maxn],len[maxn],siz[maxn];
struct edge{
int to,nxt;
}edges[maxn<<1];
void add(int u,int v){
cnt++;
edges[cnt]={v,head[u]};
head[u]=cnt;
}
stack <int> s;
void tarjan(int u){
indx++;
dfn[u]=indx;
low[u]=indx;
s.push(u);
for(int i=head[u];i;i=edges[i].nxt){
int v=edges[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!belong[v]){
low[u]=min(dfn[v],low[u]);
}
}
if(dfn[u]==low[u]){
col++;
belong[u]=col;
while(s.top()!=u){
belong[s.top()]=col;
s.pop();
}
s.pop();
}
}
void dfs(int start,int now,int step){
if(len[now]){
len[start]=len[now]+step;
return;
}
else{
dfs(start,nex[now],step+1);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&nex[i]);
if(nex[i]==i){
len[i]=1;
}
add(i,nex[i]);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
siz[belong[i]]++;
}
for(int i=1;i<=n;i++){
if(siz[belong[i]]!=1){
len[i]=siz[belong[i]];
}
}
for(int i=1;i<=n;i++){
if(!len[i]){
dfs(i,nex[i],1);
}
}
for(int i=1;i<=n;i++){
printf("%d\n",len[i]);
}
return 0;
}
4 结语
tarjan缩点是简化一张图的很好方法,它的作用不止在求强连通分量,还能去环等等,是我们处理复杂图论的好帮手。
我们在感觉做完一个题的时候,还要想想我们是不是有什么遗漏的需要特别判断。