主要由三部分组成:Tarjan算法,拓扑排序(topsort),以及动态规划(dp)。
-
Tarjan算法:这是一种用于在图中查找强连通分量或割点的算法。在这段代码中,Tarjan算法被用于找出图中的强连通分量。每个强连通分量被视为一个节点,节点的权值为该强连通分量中所有节点的权值之和。
-
拓扑排序:拓扑排序是对有向无环图(DAG)进行排序的算法,它会按照依赖关系进行排序。在这段代码中,拓扑排序被用于确定强连通分量(视为节点)之间的依赖关系,从而确定最优的遍历顺序。
-
动态规划:动态规划是一种通过把原问题分解为相互重叠的子问题来求解问题的方法。在这段代码中,动态规划被用于在给定的依赖关系下,找出权值最大的路径
对于一个路径来说,如果走入了一个强连通分量,无论如何都要走完这个强连通分量的点
缩点之后就对有向无环图(DAG)进行拓扑排序 + dp
代码的状态转移方程可以表示为:
-
对于每个节点
v
,我们遍历其所有前驱节点u
。 -
如果
f[u] > f[v]
,那么我们更新f[v] = f[u]
,并且g[v] = g[u]
。这表示如果从根节点到节点u
的路径上能集聚的点数多于从根节点到节点v
的路径,那么我们就选择从根节点到节点u
的路径,并且更新从根节点到节点v
的路径的最小权值为从根节点到节点u
的路径的最小权值。 -
如果
f[u] == f[v]
,那么我们更新g[v] = min(g[u], g[v])
。这表示如果从根节点到节点u
的路径上能集聚的点数等于从根节点到节点v
的路径,那么我们就选择这两条路径中权值最小的那条。
所以,状态转移方程可以表示为:
f[v] = max(f[u])
,其中u
是v
的所有前驱节点。g[v] = min(g[u])
,其中u
是使得f[u] = f[v]
的所有节点。
以下是代码
#include <iostream>
#include <cstring>
#include <stack>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <set>
#define mem(a,b) memset(a,b,sizeof(a))
#define rep(a, b, c) for (a = b; a <= c; a++)
#define int long long
using namespace std;
typedef double db;
typedef pair<int,int> pii;
const int N = 200010, M = 200010 * 2;
const int inf = 3e18;
int n,m;
int h[N],H[N],ne[M],e[M],idx; // 领链表
int w[N];
int dfn[N],low[N],id[N],timestamp,scc_cnt;
stack <int> stk;
bool isStk[N];
int sz[N],din[N],val[N];
void add(int h[],int a,int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void tarjan(int u){
dfn[u] = low[u] = ++ timestamp;
stk.push(u); isStk[u] = true;
for (int i = h[u] ; ~i ; i = ne[i]){
int x = e[i];
// 后向边
if (!dfn[x]){
tarjan(x);
low[u] = min(low[u],low[x]);
}
// 前向边
else if (isStk[x]){
low[u] = min(low[u],dfn[x]);
}
}
// 如果该点是连通分量的顶点
if (dfn[u] == low[u]){
int y;
scc_cnt ++;
do {
y = stk.top(); stk.pop();
isStk[y] = false;
id[y] = scc_cnt;
sz[scc_cnt] ++ ;
val[scc_cnt] += w[y];
} while (y != u);
}
}
int f[N]; // f[i] 从根到i这个点所能集聚的最多的点
int g[N]; // g[i] f[i]max的前提现 最小的g[i]
void topsort(int h[]){
queue <int> q;
int i;
rep(i,1,scc_cnt){
if (!din[i]){
q.push(i);
g[i] = 0;
}
}
while (q.size()){
int u = q.front(); q.pop();
f[u] += sz[u];
g[u] += val[u];
for (i = h[u]; ~i; i = ne[i]){
int v = e[i];
if (f[u] > f[v]){
f[v] = f[u];
g[v] = g[u];
}else if (f[u] == f[v]){
g[v] = min(g[u],g[v]);
}
din[v] --;
if (din[v] == 0) q.push(v);
}
}
int ans_long = 0, ans_val = inf;
rep(i,1,scc_cnt){
if (ans_long < f[i]){
ans_long = f[i];
ans_val = g[i];
}else if(ans_long == f[i]){
ans_val = min(ans_val,g[i]);
}
}
cout << ans_long << ' ' << ans_val << endl;
}
void solve(){
int i;
{
cin >> n >> m;
timestamp = scc_cnt = idx = 0;
while (stk.size()) stk.pop();
int i;
rep(i,1,n){
cin >> w[i];
// init
H[i] = h[i] = -1;
dfn[i] = low[i] = id[i] = 0;
isStk[i] = 0;
din[i] = val[i] = sz[i] = 0;
f[i] = 0;
g[i] = inf;
}
while (m --){
int a,b;
cin >> a >> b;
add(h,a,b);
}
rep(i,1,n){
if (!dfn[i]){
tarjan(i);
}
}
}
// 去重用的
set <int> edge;
int u;
rep(u,1,n){
for (int i = h[u]; ~i; i = ne[i]){
int v = e[i];
int A = id[u], B = id[v];
if (A != B && !edge.count(A * N + B)){
edge.insert(A * N + B);
add(H,A,B);
din[B] ++;
}
}
}
topsort(H);
}
signed main(){
cin.tie(0);
int Gold_age = 1;
cin >> Gold_age;
while (Gold_age --){
solve();
}
system("pause");
}