P3387 【模板】缩点 (tarjan SCC缩点)

【模板】缩点

题目描述

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

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

输入格式

第一行两个正整数 \(n,m\)

第二行 \(n\) 个整数,其中第 \(i\) 个数 \(a_i\) 表示点 \(i\) 的点权。

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

输出格式

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

样例 #1

样例输入 #1

2 2
1 1
1 2
2 1

样例输出 #1

2

提示

对于 \(100\%\) 的数据,\(1\le n \le 10^4\)\(1\le m \le 10^5\)\(0\le a_i\le 10^3\)

代码

// Problem: P3387 【模板】缩点
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3387
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Created Time: 2022-07-20 14:56:17
// 
// Powered by CP Editor (https://cpeditor.org)

//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=4e5+10,M=1e6+10;
int n,m,idx,h[N],hs[N],e[N],ne[N];

ll w[N],sum[N],f[N];
int low[N],dfn[N],stk[N],times,top,siz[N],scc_cnt,id[N];
bool in_stk[N];

void tarjan(int u)
{
   dfn[u]=low[u]=++times;//初始化时间戳和能访问到的最小时间戳
	//不能写成times++,因为是以dfn==0为条件判断是否被计算过的
    stk[++top]=u,in_stk[u]=true;//把当前点加入联通分量的栈
	//写++ttop是为了后面取出栈顶元素是对应,这里也可以写ttop++,同时将后面改成t=stk[--ttop]
   	for(int i=h[u];~i;i=ne[i])//遍历当前点能访问到的所有邻点
	{
		int j=e[i];
		if(!dfn[j])//如果没有计算过
		{
			tarjan(j);//递归计算
			low[u]=min(low[u],low[j]);//用邻点能访问到的最小时间戳更新当前点
		}
		else if (in_stk[j])low[u]=min(low[u],dfn[j]);//如果已经在栈中就用时间戳更新
	}

    if(low[u]==dfn[u])//如果这个点是联通分量中的最高点
	{
		++scc_cnt;//计数
		int y;
		//记录该联通分量中所有点的信息
		do
		{
			y=stk[top--]; //将连通分量里的点出栈
			in_stk[y]=false;

			id[y]=scc_cnt;//记录点在哪个连通分量
			siz[scc_cnt]++;//记录连通分量中点数

			sum[scc_cnt]+=w[y];//记录联通分量中点权总和
			
		}	while(u!=y);
	}
}
void add(int h[],int a,int b)
{
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>w[i];//输入点权
	//初始化原图和缩点后图的表头
	memset(h,-1,sizeof h);
	memset(hs,-1,sizeof hs);
	   //前向星建原图
	while(m--)
	{
		int a,b;
		cin>>a>>b;
		add(h,a,b);
	}
	//求scc
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i);


	//缩点后建立新图
	for(int i=1;i<=n;i++)
		for(int j=h[i];~j;j=ne[j])
		{
			int k=e[j];
			int a=id[i],b=id[k];
			if(a!=b)
			{
				add(hs,a,b);
				
			}
		}

	//统计路径总和最大值
	for(int i=scc_cnt;i;i--)//缩点后scc从大到小即为拓扑序,直接在DAG上递推统计答案
	{
		//如果这个点没有被计算过,则说明是起点,初始化
		if(!f[i])
		{
			f[i]=sum[i];
		}
		//遍历邻点并递推状态
		for(int j=hs[i];~j;j=ne[j])
		{
			int k=e[j];
			if(f[k]<f[i]+sum[k])
			{
				f[k]=f[i]+sum[k];
			}
		}
	}
	//计算答案
	ll ans=0;
	for(int i=1;i<=scc_cnt;i++)
		ans=max(ans,f[i]);
	
	cout<<ans<<endl;
    return 0;
}
posted @ 2022-07-20 23:36  Avarice_Zhao  阅读(78)  评论(0编辑  收藏  举报