题目描述
给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n, m
第二行 n 个整数,依次代表点权
第三至 m + 2 行,每行两个整数 u, v,表示一条 u → v 的有向边。
输出格式
共一行,最大的点权之和。
输入输出样例
输入
2 2
1 1
1 2
2 1
输出
2
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, m, cnt, s, sum, num; // n 个点,m 条边,cnt 强连通分量
int K[maxn], head[maxn], h[maxn], p[maxn], dis[maxn], in[maxn]; // K 表示每个节点所属的强联通分量
int DFN[maxn], low[maxn]; // DFN[i] 表示 i 点的进入时间,low[i]表示从 i 点出发,所能访问到的最早的进入时间
bool inS[maxn];
stack<int> S;
struct edge{
int from, to, next;
}edge1[maxn * 10], edge2[maxn * 10];
void add(int x, int y)
{
edge1[++sum].next = head[x];
edge1[sum].from = x;
edge1[sum].to = y;
head[x] = sum;
}
void Tarjan(int x)
{
num++;
DFN[x] = low[x] = num; // num 表示在栈中的编号
inS[x] = 1;
S.push(x);
for(int i = head[x]; i; i = edge1[i].next){ // 搜索相连节点
int s = edge1[i].to;
if(!DFN[s]){ // 没搜索过
Tarjan(s);
low[x] = min(low[x], low[s]); // 更新所能到的上层节点
}
else if(inS[s]){ // 在栈中
low[x] = min(low[x], DFN[s]); // 到栈中最上端的节点
}
}
if(low[x] == DFN[x]){
int y;
while(1){
y = S.top();
inS[y] = 0;
S.pop();
K[y] = x;
if(x == y)
break;
p[x] += p[y];
}
}
return ;
}
int tuopu() // 拓扑排序
{
queue<int> q;
int tot = 0;
for(int i = 1; i <= n; i++){
if(K[i] == i && !in[i]){
q.push(i);
dis[i] = p[i];
}
}
while(!q.empty()){
int k = q.front();
q.pop();
for(int i = h[k]; i; i = edge2[i].next){
int v = edge2[i].to;
dis[v] = max(dis[v], dis[k] + p[v]);
in[v]--;
if(in[v] == 0)
q.push(v);
}
}
int ans = 0;
for(int i = 1; i <= n; i++)
ans = max(ans, dis[i]);
return ans;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> p[i];
for(int i = 1; i <= m; i++){
int u, v;
cin >> u >> v;
add(u, v);
}
for(int i = 1; i <= n; i++){
if(!DFN[i])
Tarjan(i);
}
for(int i = 1; i <= m; i++){
int x = K[edge1[i].from], y = K[edge1[i].to];
if(x != y){
edge2[++s].next = h[x];
edge2[s].to = y;
edge2[s].from = x;
h[x] = s;
in[y]++;
}
}
cout << tuopu() << endl;
return 0;
}