Codeforces #447 Div2 E
阿新 • • 發佈:2017-11-23
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