Tarjan算法、Tarjan缩点模板+视频讲解+例题

P3387 【模板】缩点
题目背景
缩点+DP

题目描述
给定一个 nn 个点 mm 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

输入格式
第一行两个正整数 n,m

第二行 n 个整数,依次代表点权

第三至 m+2 行,每行两个整数 u,vu,v,表示一条 u→v 的有向边。

输出格式
共一行,最大的点权之和。
输入输出样例
输入 #1

2 2
1 1
1 2
2 1

输出 #1

2

说明/提示
【数据范围】
对于 100%100% 的数据,1≤n≤104,1<=m<=105点权 ∈[0,1000]

算法:Tarjan 缩点 + DAGdp
视频讲解地址:https://www.bilibili.com/video/av70802550
(声音有点小,这个视频是B站找的,不是本人做的)
题解:就是Tarjan缩点+记忆化搜索
详情请看:https://www.luogu.com.cn/blog/user23342/solution-p3387
(代码参考于上两个链接)

关于Tarjan算法都在上面视频了,这里只给出模板(有详细注释):

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<algorithm>
#include<cmath>
#include<vector> 
#include<stack>
using namespace std;
int d[10005];//每个边的权值
vector<int> e[10005];//图
stack<int> s;//dfs栈 
int dfn[10005];//记录dfs次序
int low[10005];//每个顶点能回到的最早时间戳
int pre=0;//时间戳 
int color[10005];//记录每个顶点属于的SCC
int x[100005],y[100005];//x[i]->y[i]输入的第i条边 
int scc=0;
int sum[10005];//缩点后的权值 
int f[10005];//新图每个顶点的如果没访问过就为0,访问过就是从当前点能走到最远的值
void init(int n)
{
	for(int i=1;i<=n;i++) e[i].clear() ;
	return;
}
void Tarjan(int u)//就是dfs 
{
	low[u]=dfn[u]=++pre;//记录时间戳,刚遍历到,这两个值当然相同
	s.push(u);//入栈
	for(int i=0;i<e[u].size() ;i++){
		int v=e[u][i];//u->v
		if(!dfn[v])
		{
			Tarjan(v);//继续dfs
			low[u]=min(low[u],low[v]);//通过子节点v能回到的最早时间戳 
		}
		else if(!color[v])//如果vdfs访问过且v不是以前dfs的(即color值大于等于1,不在栈s里),而是当前dfs栈中的点
		low[u]=min(low[u],dfn[v]);//就能通过v更新当前u能回到的最早时间戳 
	}
	if(low[u]==dfn[u])//判断u是否为某SCC的第一个被发现的顶点 这样才能确定最大的(强连通分量) 
	{
		scc++;//第几个SCC
		while(!s.empty() )
		{
			int x=s.top() ;s.pop() ;//出栈
			color[x]=scc;//记录强连通分量
			sum[scc]+=d[x];//缩点后要把当前整个SCC的权值合起来! 
			if(x==u) break;//当前强连通分量记录完毕 
		}//(染色) 
	}
	return;
}
int dfs(int x)
{
	if(f[x]) return f[x];
	f[x]=sum[x];//记忆化
	int maxx=0; 
	for(int i=0;i<e[x].size() ;i++)
	//if(!f[e[x][i]])//多了这个只有40分,必须要算上所有的出边 
	maxx=max(maxx,dfs(e[x][i]));
	f[x]=f[x]+maxx;//选最长的走 
	return f[x];
}
int main(void)
{
	int n,m,u,v;
	//1.输入 
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&d[i]);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&x[i],&y[i]);
		e[x[i]].push_back(y[i]);  //一条x[i]->y[i]的有向边
	}
	//2.Tarjan:
	pre=0;
	for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);//没有访问就dfs 
	//这图可能是不连通的或者其他不能一次dfs就访问完所有点的图 
	//-----------以上就是Tarjan算法 ------------------------- 
	//3.通过步骤2 重新建图 : scc->DAG(有向有环图->有向无环图)
	init(n);//清空原图
	for(int i=1;i<=m;i++)//每一条边 
	if(color[x[i]]!=color[y[i]]) 
	e[color[x[i]]].push_back(color[y[i]]);//给两个不同的强连通分量连方向同原来的一条边 
	//-----------以上就是 Tarjan缩点 ------------------------
	//4.对生成的新图进行记忆化搜索解答
	int ans=0; 
	for(int i=1;i<=scc;i++){
		if(!f[i])//没访问过 
		{
			ans=max(ans,dfs(i));
		}
	}
	printf("%d\n",ans);
	return 0; 
}
发布了68 篇原创文章 · 获赞 15 · 访问量 9000

猜你喜欢

转载自blog.csdn.net/qq_43791377/article/details/104386392
今日推荐