luoguP3387(强连通分量模板)

题目描述

【模板】缩点(传送门)

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和.

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

输入格式

第一行两个正整数 n,m.
第二行n个整数,依次代表点权.
第三至m+2行,每行两个整数u,v表示一条 u → v {u\rightarrow v } uv的有向边.

输出格式

共一行,最大的点权之和。

输入输出样例
输入 #1
2 2
1 1
1 2
2 1
输出 #1
2
说明/提示
【数据范围】
对 于 100 % 的 数 据 , 1 ≤ n ≤ 1 0 4 , 1 ≤ m ≤ 1 0 5 , 点 权 ∈ [ 0 , 1000 ] {对于 100\%的数据,1\le n \le 10^4,1\le m \le 10^5,点权 \in [0,1000]} 100%,1n104,1m105,[0,1000].

模板说明

原题背景已经告诉我们要用 t a r j a n 缩 点 + D A G d p {tarjan缩点+DAGdp} tarjan+DAGdp了.DAGdp即 拓 扑 排 序 + 简 单 D P {拓扑排序+简单DP} +DP.

由于是模板题我就简要说以说思路:

1.预处理+tarjan缩点

这个如果会模板的话完全是"有手就行"(但注意缩点后要将一个强连通分量的所有点的权值都加在一个代表节点上并化为一个集合,具体内容于代码中解释).

2.重新建图+拓扑排序+DP

将缩点处理后的点重新相连,新图必然无环,所以对该图的点统计其入度,并进行拓扑排序,设 d p [ i ] {dp[i]} dp[i]为走到i节点时的最大点权和,而对于这种情况显然有状态转移方程:(y为节点x指向的一个节点)
d p [ y ] = m a x ( d p [ y ] , d p [ x ] + v a l [ y ] ) {dp[y]=max(dp[y],dp[x]+val[y])} dp[y]=max(dp[y],dp[x]+val[y]);(边遍历边更新状态)
最后 a n s = m a x ( d p [ i ] ) , 1 ≤ i ≤ n , i ∈ z {ans=max(dp[i]),1≤i≤n,i∈z} ans=max(dp[i]),1in,iz.

代码

#include
#define N 200005
#define in read()
using namespace std;

int n,m,tot1,tot2,ans,sum;//ans记录最终答案,sum记录强连通分量数(本题无用)
int fi1[N],fi2[N],rd[N],qlt[N];//rd[]记录入度,qlt[]记录节点i所在的强连通分量编号(本题无用)
int Stack[N],top;//栈和指针
bool use[N];//记录节点i是否在栈中
int dfn[N],low[N],tim;
//dfn[i]代表节点i被遍历到的次序,时间戳不可改变.
//low[u]代表该子树中,且仍在栈中的最小时间戳.
int val[N],dp[N],huan[N];//val[]为各点权值,huan[]为环中的代表节点.
struct njb{
	int nxt,to,from;//from记录指向节点,to记录被指向节点.
};
njb E1[N],E2[N];//原新图各用一个邻接表

inline int in{
	int i=0;char ch;
	while(!isdigit(ch)){ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
	return i;
}//简化版快读优化

inline void lian(int u,int v)
{
	E1[++tot1].nxt=fi1[u];
	fi1[u]=tot1;
	E1[tot1].from=u;
	E1[tot1].to=v;
}//建原图

inline void tarjan(int u)//tarjan缩点
{
	dfn[u]=low[u]=++tim;
	Stack[++top]=u;//入栈
	use[u]=1;//标记已入栈
	for(int i=fi1[u];i;i=E1[i].nxt)
	{
		int v=E1[i].to;
		if(!dfn[v])//若未走过
		{
			tarjan(v);//继续往下走
			low[u]=min(low[u],low[v]);//缩小u到根的时间戳
		}
		else if(use[v])low[u]=min(low[u],dfn[v]);//否则,若v在栈中,缩小u到根的时间戳.
	}
	if(dfn[u]==low[u])//相等则u为新的强连通分量的根
	{
		//++sum;//增加强连通分量
		int v;
		while(1)
		{
			//qlt[v]=sum;//标记其所在强联通分量的编号
			v=Stack[top--];//没用了出栈
			huan[v]=u;//标记v所在强联通分量的代表节点编号
			use[v]=0;//消除在栈中的标记
			if(u!=v)val[u]+=val[v];//将权值都累加在代表节点上
			else break;//回到自己就结束
		}
	}
}

inline void topu()//拓扑排序
{
	queue<int>q;
	for(int i=1;i<=n;i++)
	if(huan[i]==i&&!rd[i])
	{
		q.push(i);//入度为零就进队
		dp[i]=val[i];//初始化DP状态
	}
	while(!q.empty())//在拓扑中DP
	{
		int x=q.front();
		q.pop();
		for(int i=fi2[x];i;i=E2[i].nxt)
		{
			int y=E2[i].to;
			dp[y]=max(dp[y],dp[x]+val[y]);//DP核心方程
			rd[y]--;
			if(!rd[y])
			q.push(y);
		}
	}
	for(int i=1;i<=n;i++)
	ans=max(ans,dp[i]);//统计最大值
	printf("%d\n",ans);
	return;
}

inline void Init()
{
	n=in,m=in;
	for(int i=1;i<=n;i++)
	val[i]=in;
	for(int i=1;i<=m;i++)
	{
		int x=in,y=in;
		lian(x,y);
	}
	for(int i=1;i<=n;i++)
	if(!dfn[i])tarjan(i);//处理没走过的点
	for(int i=1;i<=m;i++)//构建新图
	{
		int x=huan[E1[i].from];
		int y=huan[E1[i].to];//只需连接代表元素即可
		if(x!=y)//用新的邻接表存图
		{
			E2[++tot2].nxt=fi2[x];
			fi2[x]=tot2;
			E2[tot2].from=x;
			E2[tot2].to=y;
			rd[y]++;
		}
	}
	return;
}

int main()
{
	Init();
	topu();
	return 0;
}

若拓扑排序还不熟练的萌新们可以点这里一道拓扑排序经典好题.
完结撒花qwq!!!

你可能感兴趣的:(———图论———,tarjan算法,缩点-强连通分量)