【模板】缩点
题目描述
给定一个 n n n 个点 m m m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n , m n,m n,m
第二行 n n n 个整数,其中第 i i i 个数 a i a_i ai 表示点 i i i 的点权。
第三至 m + 2 m+2 m+2 行,每行两个整数 u , v u,v u,v,表示一条 u → v u\rightarrow v u→v 的有向边。
输出格式
共一行,最大的点权之和。
样例 #1
样例输入 #1
2 2
1 1
1 2
2 1
样例输出 #1
2
提示
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 4 1\le n \le 10^4 1≤n≤104, 1 ≤ m ≤ 1 0 5 1\le m \le 10^5 1≤m≤105, 0 ≤ a i ≤ 1 0 3 0\le a_i\le 10^3 0≤ai≤103。
理论
代码
//缩点
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int N = 1e5+10,M = 4*N;
int e[M],ne[M],w[M],h[N],hs[N],idx;
bool st[N];
bool in_stk[N];
int dfn[N],low[N],tm;
int stk[N],id[N],scc_cnt,scc_w[N];
int scc_size[N],f[N];
int din[N];
int n,m;
int top;
queue<int>q;
void add(int h[],int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void targin(int u){
low[u]=dfn[u]=++tm;
stk[++top]=u,in_stk[u]=true;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
targin(j);
low[u]=min(low[u],low[j]);
}else if(in_stk[j]){
low[u]=min(low[u],dfn[j]);
}
}
if(dfn[u]==low[u]){
int y;
scc_cnt++;
do{
y=stk[top--];
in_stk[y]=false;
id[y]=scc_cnt;
scc_size[scc_cnt]++;
}while(y!=u);
}
}
signed main(){
cin>>n>>m;
memset(h,-1,sizeof h);
memset(hs,-1,sizeof hs);
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
add(h,a,b);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
targin(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);
din[b]++;
}
}
scc_w[id[i]]+=w[i];//点权佳佳
}
for(int i=1;i<=scc_cnt;i++){
if(!din[i]){
q.push(i);
st[i]=true;
f[i]=scc_w[i];
}
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=hs[t];~i;i=ne[i]){
int j=e[i];
if(st[j])continue;
f[j]=max(f[j],f[t]+scc_w[j]);
if((--din[j])==0){
q.push(j);
st[j]=true;
}
}
}
int res=0;
for(int i=1;i<=n;i++){
res=max(res,f[i]);
}
cout<<res;
return 0;
}