P3627 抢掠计划

P3627 抢掠计划

问题

P3627 [APIO2009] 抢掠计划
城市中的道路都是单向的,每个路口都有一台存放一定现金的 ATM,部分路口还设有酒吧。强盗从指定的市中心出发,可以重复经过路口和道路,但每台 ATM 的现金只能抢一次;他最终必须到达某个酒吧,要求计算最多能够抢到多少钱。

数据规模:N,M≤5×105N,M\le 5\times 10^5,点权不超过 40004000。

要做一个类似 DP 的问题,不难想到拓扑排序,但本题的图是可能存在环的,于是考虑可以使用 SCC 缩点,缩点后在 DAG 上跑一次拓扑排序 DP 即可。

此时定义 dp[c] 表示从起点 SCC 到达分量 cc 时最多取得的钱,沿拓扑序处理跨 SCC 边 u→vu\to v 时,用 dp[u] + scc_val[v] 更新 dp[v],最后只在包含酒吧的 SCC 中取最大值。

注:构建 DAG 时没有去除平行边,不过因为每条平行边都同步增加并减少入度,因此不会破坏拓扑过程。若问题改成路径计数,重边会改变方案数,此时就必须去重。

Tarjan、建图与拓扑 DP 的总时间复杂度为 O(N+M)O(N+M),空间复杂度为 O(N+M)O(N+M)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
struct Edge { int v; ll w; };
vector<Edge> adj[maxn];
vector<Edge> dag[maxn];

int dfn[maxn], low[maxn], timer;
int scc[maxn], scnt;
ll scc_val[maxn];

int stk[maxn], top;
bool ins[maxn];

ll val[maxn];
int bar[maxn];
bool vis[maxn];

int ind[maxn];
ll dp[maxn];

void tarjan( int u ) {
dfn[u] = low[u] = ++ timer;
stk[++ top] = u, ins[u] = true;

for( auto [v, w] : adj[u] ) {
if( !dfn[v] ) {
tarjan( v );
low[u] = min( low[u], low[v] );
} else if( ins[v] ) {
low[u] = min( low[u], dfn[v] );
}
}

if( low[u] == dfn[u] ) {
scnt ++; int v;
do {
v = stk[top --];
ins[v] = 0;
scc[v] = scnt;
scc_val[scnt] += val[v];
} while( v != u );
}
}

void solve( ) {
int n = read( ), m = read( );

for( int i = 1; i <= m; ++ i ) {
int u = read( ), v = read( );
adj[u].push_back({ v, 0 });
}

for( int i = 1; i <= n; ++ i ) {
val[i] = read( );
}

int S = read( ), P = read( );

for( int i = 1; i <= P; ++ i ) {
bar[i] = read( );
}

for( int i = 1; i <= n; ++ i ) {
if( !dfn[i] ) tarjan( i );
}

for( int u = 1; u <= n; ++ u ) {
for( auto [v, w] : adj[u] ) {
if( scc[u] != scc[v] ) {
dag[scc[u]].push_back({ scc[v], 0 });
ind[scc[v]] ++;
}
}
}

for( int i = 1; i <= P; ++ i ) {
vis[ scc[bar[i]] ] = 1;
}

for( int i = 1; i <= scnt; ++ i ) dp[i] = -inf;

int st = scc[S];
dp[st] = scc_val[st];

queue<int> q;

for( int i = 1; i <= scnt; ++ i ) {
if( ind[i] == 0 ) q.push( i );
}

while( !q.empty( ) ) {
int u = q.front( ); q.pop( );
for( auto [v, w] : dag[u] ) {
if( dp[u] != -inf ) {
dp[v] = max( dp[v], dp[u] + scc_val[v] );
}
if( -- ind[v] == 0 ) q.push( v );
}
}

ll ans = 0;
for( int i = 1; i <= scnt; ++ i ) {
if( vis[i] ) ans = max( ans, dp[i] );
}
cout << ans << '\n';
}

缩点后的问题就是带点权 DAG 上的最长路,不过只有从起点分量可达的状态才能参与转移。题目保证至少有一个酒吧可达,因此最终一定能在被标记的酒吧分量中取得有效答案。

记录

  • 归属:SCC 缩点、拓扑排序;
  • 新增:允许重复行走使 SCC 内点权可以一次性合并,缩点后再在 DAG 上从指定起点做带可达性标记的最长路。
  • 分类:待定

P3627 抢掠计划
http://miqiu.space/posts/p3627001/
作者
Miqiu
发布于
2026年8月10日
许可协议