1. 程式人生 > >Codeforces #447 Div2 E

Codeforces #447 Div2 E

mem blog mar ack 重新 out 強連通分量 sta bit

#447 Div2 E

題意

給出一個由有向邊構成的圖,每條邊上有蘑菇,假設有 \(n\) 個蘑菇,那麽第一次走過這條邊可以獲得 \(n\) 個蘑菇,第二次 \(n-1\),第三次 \(n-1-2\),第四次 \(n-1-2-3\),後面類推,直至為 \(0\)。問從選定點出發最多可以獲得幾個蘑菇。

分析

Tarjan 算法縮點,重新給點標號(縮點),且保證了拓撲排序中靠後的點先標號,對於縮完點後的有向無環圖,DP去求最長路。(對於拓撲排序後的序列,根據拓撲排序的性質,可以從後往前DP)
拓撲排序保證了:對於有向邊 \(a-b\)\(a\) 一定在 \(b\) 前面。

code

#include <bits/stdc++.h>
using namespace std; typedef long long ll; const int N = 2e6 + 10; struct Edge { int v, w, nxt; }e[N]; int head[N], cnt; void addEdge(int u, int v, int w) { e[cnt].v = v; e[cnt].w = w; e[cnt].nxt = head[u]; head[u] = cnt++; } int n, m, c, nn, vis[N], dfn[N], low[N]; int f[N]; // 被縮成的新點的序號
ll sup[N]; // 這個新點能提供的貢獻 stack<int> sta; vector<int> G[N]; void tarjan(int u) { // 找強連通分量 sta.push(u); dfn[u] = low[u] = ++c; vis[u] = 1; for(int i = head[u]; ~i; i = e[i].nxt) { int v = e[i].v; if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else
if(vis[v] && low[u] > dfn[v]) { low[u] = dfn[v]; } } if(low[u] == dfn[u]) { ++nn; while(1) { int id = sta.top(); G[nn].push_back(id); f[id] = nn; sta.pop(); vis[id] = 0; if(id == u) break; } } } ll calc(int w) { int d = sqrt(2 * w); while(d * d + d > 2 * w) d--; return 1LL * w * (d + 1) - (1LL * d * (d + 1) * (2 * d + 1) / 6 + d * (d + 1) / 2) / 2; } ll dp[N]; int main() { memset(head, -1, sizeof head); scanf("%d%d", &n, &m); for(int i = 0; i < m; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); addEdge(u, v, w); } nn = n; for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i); } // 計算每個強連通分量縮成的點能提供的貢獻 for(int i = 1; i <= n; i++) { for(int j = head[i]; ~j; j = e[j].nxt) { int v = e[j].v; if(f[i] == f[v]) sup[f[i]] += calc(e[j].w); } } int s; scanf("%d", &s); s = f[s]; for(int i = n + 1; i <= nn; i++) { for(int j = 0; j < G[i].size(); j++) { int q = G[i][j]; for(int p = head[q]; ~p; p = e[p].nxt) { int v = e[p].v; if(f[q] != f[v]) dp[i] = max(dp[i], dp[f[v]] + sup[f[v]] + e[p].w); } } } cout << sup[s] + dp[s] << endl; return 0; }

Codeforces #447 Div2 E