Tarjan
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int read(){
int x;scanf("%d",&x);return x;
}
struct{
int y[2],nxt[2],x;
}e[N];
int len,Link[N];
void add(int x,int y,int op){
e[++len].x=x;
e[len].y[op]=y;
e[len].nxt[op]=Link[x];
Link[x]=len;
}
int dfn[N],low[N],scc[N],s[N],a[N],vis[N];
int cnt,top;
void tarjan(int x){
dfn[x]=low[x]=++cnt;
s[++top]=x;
vis[x]=1;
for(int i=Link[x];i;i=e[i].nxt[0]){
int y=e[i].y[0];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else
if(vis[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
while(1){
int y=s[top--];
scc[y]=x;
vis[y]=0;
if(x==y) break;
a[x]+=a[y];
}
}
}
int dis[N],d[N];
int n,m;
void topu(){
queue<int>q;
int tot=0;
for(int i=1;i<=n;i++)
if(scc[i]==i&&!d[i])
q.push(i),dis[i]=a[i];
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=Link[x];i;i=e[i].nxt[1]){
int y=e[i].y[1];
dis[y]=max(dis[y],dis[x]+a[y]);
if(--d[y]==0) q.push(y);
}
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++) add(read(),read(),0);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
len=0;
memset(Link,0,sizeof Link);
for(int i=1;i<=m;i++){
int x=scc[e[i].x],y=scc[e[i].y[0]];
if(x!=y){
add(x,y,1);
d[y]++;
}
}
topu();
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,dis[i]);
printf("%d",ans);
return 0;
}
代码解释:有向图的强连通分量与最长路径
这段代码主要解决了一个有向图中的问题:找到图中权值和最大的路径,其中每个节点的权值可以累加。代码使用了Tarjan算法来找到强连通分量(SCC),然后构建缩点图,最后通过拓扑排序计算最长路径。
代码结构
- 头文件和常量定义
- 数据结构定义
- Tarjan算法实现
- 拓扑排序计算最长路径
- 主函数
详细解释
1. 头文件和常量定义
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
- 包含所有标准库头文件
- 定义常量N为100010,表示最大节点数
2. 数据结构定义
int read(){
int x;scanf("%d",&x);return x;
}
struct{
int y[2],nxt[2],x;
}e[N];
int len,Link[N];
read()函数:快速读取整数- 边结构体
e:存储图的边信息y[0]和y[1]:分别用于原图和缩点图的邻接表nxt[0]和nxt[1]:分别用于原图和缩点图的邻接表指针x:边的起点
len:边计数器Link[N]:邻接表头指针数组
3. 添加边函数
void add(int x,int y,int op){
e[++len].x=x;
e[len].y[op]=y;
e[len].nxt[op]=Link[x];
Link[x]=len;
}
op参数决定是添加到原图(op=0)还是缩点图(op=1)- 使用链式前向星存储邻接表
4. Tarjan算法实现
int dfn[N],low[N],scc[N],s[N],a[N],vis[N];
int cnt,top;
void tarjan(int x){
dfn[x]=low[x]=++cnt;
s[++top]=x;
vis[x]=1;
for(int i=Link[x];i;i=e[i].nxt[0]){
int y=e[i].y[0];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else
if(vis[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
while(1){
int y=s[top--];
scc[y]=x;
vis[y]=0;
if(x==y) break;
a[x]+=a[y];
}
}
}
dfn[x]:节点x的DFS序low[x]:节点x能回溯到的最早节点scc[x]:节点x所属的强连通分量的代表节点s[]:栈用于Tarjan算法a[]:节点权值数组,缩点时累加同一SCC内节点的权值vis[x]:标记节点是否在栈中
算法步骤:
- 初始化dfn和low值
- 将当前节点压栈并标记
- 遍历所有邻接节点
- 如果邻接节点未访问,递归处理
- 如果邻接节点在栈中,更新low值
- 如果当前节点是SCC的根,弹出栈中节点直到当前节点,形成SCC
5. 拓扑排序计算最长路径
int dis[N],d[N];
int n,m;
void topu(){
queue<int>q;
int tot=0;
for(int i=1;i<=n;i++)
if(scc[i]==i&&!d[i])
q.push(i),dis[i]=a[i];
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=Link[x];i;i=e[i].nxt[1]){
int y=e[i].y[1];
dis[y]=max(dis[y],dis[x]+a[y]);
if(--d[y]==0) q.push(y);
}
}
}
dis[x]:从某个起点到x的最大路径权值和d[x]:缩点图中节点的入度- 算法步骤:
- 初始化队列,将入度为0的SCC加入队列
- 对于每个节点,更新其邻接节点的最大距离
- 减少邻接节点的入度,如果入度为0则加入队列
6. 主函数
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=m;i++) add(read(),read(),0);
// 求强连通分量
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
// 构建缩点图
len=0;
memset(Link,0,sizeof Link);
for(int i=1;i<=m;i++){
int x=scc[e[i].x],y=scc[e[i].y[0]];
if(x!=y){
add(x,y,1);
d[y]++;
}
}
// 拓扑排序求最长路径
topu();
// 找出最大距离
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,dis[i]);
printf("%d",ans);
return 0;
}
主函数流程:
- 读取节点数n和边数m
- 读取每个节点的权值
- 读取并添加原图的边
- 使用Tarjan算法求强连通分量
- 构建缩点图(SCC之间的边)
- 对缩点图进行拓扑排序计算最长路径
- 找出并输出最大路径权值和
算法总结
- Tarjan算法:将原图分解为强连通分量(SCC),每个SCC被视为一个超级节点
- 缩点图构建:将原图中的边转换为SCC之间的边,去除SCC内部的边
- 拓扑排序:在DAG(有向无环图)上计算最长路径
这种方法有效地将原问题转化为在DAG上求最长路径的问题,可以利用拓扑排序高效解决。

浙公网安备 33010602011771号