题目链接:https://codeforces.com/problemset/problem/894/E
10.1 题意
给定一个 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1≤n≤106) 个点和 m ( 0 ≤ m ≤ 1 0 6 ) m(0 \le m \le 10^6) m(0≤m≤106) 条边的有向图,每条边 i i i 的权值为 w i ( 0 ≤ w i ≤ 1 0 8 ) w_i(0 \le w_i \le 10^8) wi(0≤wi≤108)。第 k k k 次经过边 i i i 会得到 max ( 0 , w i − k ( k − 1 ) 2 ) \max(0,w_i-\frac{k(k-1)}{2}) max(0,wi−2k(k−1)) 的收益。
问从 s s s 出发,可以获得的最大总收益。
10.2 解题过程
容易发现,在同一个强连通分量中的边可以反复经过,因此强连通分量中的所有边都可以其最大价值。
考虑使用 Tarjan 缩点,缩点之后形成一张有向无环图。
对于这张有向无环图上的边,只能经过一次,因此只能获得 w i w_i wi 的价值。
设 s u m ( n ) = ∑ i = 1 n i = n ( 1 + n ) 2 sum(n)=\sum_{i=1}^n i=\frac{n(1+n)}{2} sum(n)=∑i=1ni=2n(1+n), p r e f i x ( n ) = ∑ i = 1 n s u m ( i ) prefix(n)=\sum_{i=1}^n sum(i) prefix(n)=∑i=1nsum(i)。
现在我们要计算强连通分量中每一条边对答案的贡献。
设某一条边被经过了 k k k 次,则我们得到的权值之和为:
∑ i = 0 k ( w − s u m ( k ) ) \sum_{i=0}^k (w-sum(k)) i=0∑k(w−sum(k))
其中, w − s u m ( k ) w-sum(k) w−sum(k) 必须满足 w − s u m ( k ) ≥ 0 w-sum(k) \ge 0 w−sum(k)≥0,解此一元二次方程得到 k k k 的取值范围为 [ 0 , 8 w + 1 2 ] [0, \frac{\sqrt{8w+1}}{2}] [0,28w+1]。
因此,这条边对答案的贡献为 ( k + 1 ) ⋅ w − p r e f i x ( k ) (k+1)\cdot w - prefix(k) (k+1)⋅w−prefix(k)。
设点 u u u 所属的联通分量为 c o l [ u ] col[u] col[u]。
对于每一条强连通分量中的边 < u , v > <u,v> <u,v>,将其贡献加入到 a [ c o l [ u ] ] a[col[u]] a[col[u]] 中。
在进行拓扑排序之后,需要倒着跑 d p dp dp 转移。
设
d
p
[
c
o
l
[
u
]
]
dp[col[u]]
dp[col[u]] 为从
u
u
u 这一联通分量出发可以得到的最大价值,则对于连通分量之外的边
<
u
,
v
,
w
>
<u,v,w>
<u,v,w>,有如下转移
d
p
[
c
o
l
[
u
]
]
=
max
(
d
p
[
c
o
l
[
u
]
]
,
d
p
[
c
o
l
[
v
]
]
+
a
[
c
o
l
[
u
]
]
+
w
)
dp[col[u]] = \max(dp[col[u]],dp[col[v]]+a[col[u]]+w)
dp[col[u]]=max(dp[col[u]],dp[col[v]]+a[col[u]]+w)
最终的答案为
d
p
[
c
o
l
[
s
]
]
dp[col[s]]
dp[col[s]]。
时间复杂度: O ( n ) O(n) O(n)。
10.3 代码
int head[maxn], n, m, cnt, tot, top, num, s;
int dfn[maxn], low[maxn], st[maxn], col[maxn], indeg[maxn], topo[maxn], pos[maxn];
bool instack[maxn];
ll a[maxn], prefix[maxn], dp[maxn];
struct edge
{
int u, v, nxt;
ll w;
} Edge[maxn];
void init()
{
for(int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void addedge(int u, int v, ll w)
{
Edge[cnt].u = u;
Edge[cnt].v = v;
Edge[cnt].w = w;
Edge[cnt].nxt = head[u];
head[u] = cnt++;
}
void tarjan(int id)
{
dfn[id] = low[id] = ++tot;
st[++top] = id;
instack[id] = true;
for(int i = head[id]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[id] = min(low[id], low[v]);
}
else if(instack[v])
low[id] = min(low[id], dfn[v]);
}
if(dfn[id] == low[id])
{
int v;
num++;
do
{
v = st[top--];
instack[v] = false;
col[v] = num;
} while(v != id);
}
}
void toposort()
{
queue<int> que;
for(int i = 1; i <= num; i++)
{
if(!indeg[i]) que.push(i);
}
int flag = 0;
while(!que.empty())
{
int now = que.front();
que.pop();
topo[++flag] = now;
for(int i = head[now]; i != -1; i = Edge[i].nxt)
{
int v = Edge[i].v;
--indeg[v];
if(!indeg[v]) que.push(v);
}
}
}
int main()
{
int u, v;
ll w;
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m; i++)
{
scanf("%d%d%lld", &u, &v, &w);
addedge(u, v, w);
}
scanf("%d", &s);
for(int i = 1; i <= n; i++)
{
if(!dfn[i]) tarjan(i);
}
init();
ll sum = 0;
for(int i = 1; i <= 20000; i++)
{
sum = sum + 1LL * i;
prefix[i] = prefix[i - 1] + sum;
}
for(int i = 0; i < m; i++)
{
u = Edge[i].u;
v = Edge[i].v;
w = Edge[i].w;
if(col[u] == col[v])
{
ll k = (sqrt(8.0 * w + 1.0) - 1) / 2;
a[col[u]] += ((k + 1) * w - prefix[k]);
continue;
}
else
{
addedge(col[u], col[v], w);
indeg[col[v]]++;
}
}
toposort();
for(int i = 1; i <= num; i++) pos[topo[i]] = i;
for(int i = num; i >= 1; i--)
{
int u = topo[i];
dp[u] = a[u];
for(int j = head[u]; j != -1; j = Edge[j].nxt)
{
int v = Edge[j].v;
if(pos[v] > pos[u]) dp[u] = max(dp[u], dp[v] + a[u] + Edge[j].w);
}
}
printf("%lld\n", dp[col[s]]);
return 0;
}