Tarjan

P3387 缩点

#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),然后构建缩点图,最后通过拓扑排序计算最长路径。

代码结构

  1. 头文件和常量定义
  2. 数据结构定义
  3. Tarjan算法实现
  4. 拓扑排序计算最长路径
  5. 主函数

详细解释

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]:标记节点是否在栈中

算法步骤:

  1. 初始化dfn和low值
  2. 将当前节点压栈并标记
  3. 遍历所有邻接节点
  4. 如果邻接节点未访问,递归处理
  5. 如果邻接节点在栈中,更新low值
  6. 如果当前节点是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]:缩点图中节点的入度
  • 算法步骤:
    1. 初始化队列,将入度为0的SCC加入队列
    2. 对于每个节点,更新其邻接节点的最大距离
    3. 减少邻接节点的入度,如果入度为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;
}

主函数流程:

  1. 读取节点数n和边数m
  2. 读取每个节点的权值
  3. 读取并添加原图的边
  4. 使用Tarjan算法求强连通分量
  5. 构建缩点图(SCC之间的边)
  6. 对缩点图进行拓扑排序计算最长路径
  7. 找出并输出最大路径权值和

算法总结

  1. Tarjan算法:将原图分解为强连通分量(SCC),每个SCC被视为一个超级节点
  2. 缩点图构建:将原图中的边转换为SCC之间的边,去除SCC内部的边
  3. 拓扑排序:在DAG(有向无环图)上计算最长路径

这种方法有效地将原问题转化为在DAG上求最长路径的问题,可以利用拓扑排序高效解决。

posted @ 2025-07-28 14:28  追旭  阅读(29)  评论(0)    收藏  举报
//雪花飘落效果