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;
}
一个菜鸡