【模板】縮點(tarjan + 拓撲排序)
阿新 • • 發佈:2021-01-30
題目描述
給定一個 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;
}