tarjan习题讲解
题目描述
有 \(n\) 个点, \(m\) 条边, 每次经过一条路径, 路径上蘑菇减少, 求出从 \(s\) 开始捡到的最多蘑菇。
思路
可以先缩点, 求出一个大点内的最大蘑菇, 然后做 \(DAG\) 上的 DP
推式子, 可以发现, 蘑菇数为 \(x\) 的最大贡献为 : 记 \(m \le 0\) 且 \((1 + m) \cdot m / 2 <= x\) 且 \((1 + m + 1) \cdot (m + 1) > x\)。
贡献为 \((m + 1) \cdot x - ((m + 1) \cdot (2m + 1) \cdot m / 6 + m(m + 1) / 2)\)。
时空均为 \(O(n + m)\)
代码
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e6 + 5;
struct Node{
int a, b;
};
vector<Node>g[N], e[N];
long long dp[N], w[N], ans;
int l, r, mid, dfn[N], cnt, tot, top, low[N], stk[N], n, m, u, v, ww, s, color[N], in[N];
LL cost(int x){
l = 0, r = 20000;
while(l < r){
mid = (l + r + 1) >> 1;
if(1ll * mid * (mid + 1) / 2 <= x){
l = mid;
}
else{
r = mid - 1;
}
}
return 1ll * (l + 1) * x - (1ll * (l + 1) * (l * 2 + 1) * l / 6 + 1ll * l * (l + 1) / 2) / 2;
}
void tarjan(int x){
dfn[x] = low[x] = ++cnt, stk[++top] = x;
for(auto [v, w] : g[x]){
if(!dfn[v]){
tarjan(v);
low[x] = min(low[x], low[v]);
}
else if(!color[v]){
low[x] = min(low[x], dfn[v]);
}
}
if(low[x] == dfn[x]){
++tot;
for(; dfn[stk[top]] >= dfn[x]; color[stk[top]] = tot, top--){
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; ++i){
cin >> u >> v >> ww;
g[u].push_back({v, ww});
}
for(int i = 1; i <= n; ++i){
if(!dfn[i]){
tarjan(i);
}
}
for(int i = 1; i <= n; ++i){
for(auto [v, op] : g[i]){
if(color[v] != color[i]){
e[color[i]].push_back({color[v], op});
in[color[v]]++;
}
else{
w[color[i]] += cost(op);
}
}
}
vector<int> pq;
for(int i = 1; i <= tot; ++i){
dp[i] = -1e18;
if(!in[i]){
pq.push_back(i);
}
}
cin >> s;
dp[color[s]] = w[color[s]];
while(pq.size()){
int i = pq.back();
pq.pop_back();
for(auto [x, op] : e[i]){
dp[x] = max(dp[x], dp[i] + w[x] + op);
in[x]--;
if(!in[x]){
pq.push_back(x);
}
}
ans = max(ans, dp[i]);
}
cout << ans;
return 0;
}