1. 题面梗概
中译中 :给一个有向图。对于每一个 k ∈ [ 1 , N ] k \in [1, N] k ∈ [ 1 , N ] ,问最少删掉多少个点,能保证从 1 1 1 号点出发,无论怎么走都走不出 { 1 , … , k } \{1, \dots, k\} { 1 , … , k } 。
N , M ≤ 3 × 10 5 N, M \leq 3 \times 10^5 N , M ≤ 3 × 1 0 5
2. 逻辑推导
给定 N N N 次查询,总不能每次都鲨鲨去搜一遍吧,那样复杂度肯定爆。
2.1 预处理
为了快速判断一个点 v v v 能否在 “仅经过编号 ≤ k \leq k ≤ k 的点” 的情况下被 1 1 1 号点到达,我们定义 f [ v ] f[v] f [ v ] :
从节点 1 1 1 到节点 v v v 的所有路径中,路径上最大节点的编号的最小值。
如果 f [ v ] ≤ k f[v] \leq k f [ v ] ≤ k ,意味着从 1 → v 1\to v 1 → v 上存在一条全部节点 ≤ k \leq k ≤ k 的路径。
如果 f [ v ] > k f[v] > k f [ v ] > k ,则意味着要到达 v v v ,必须经过至少一个编号大于 k k k 的点。
2.2 类Dijkstra
计算 f [ v ] f[v] f [ v ] 本质上是一个变体的最短路问题:
初始化 :f [ 1 ] = 1 f[1] = 1 f [ 1 ] = 1 (起点的瓶颈值设为 1),其余 f [ v ] = ∞ f[v] = \infty f [ v ] = ∞ 。
转移 :对于边 u → v u \to v u → v ,新的潜在可能值为 max ( f [ u ] , u ) \max(f[u], u) max ( f [ u ] , u ) 。
松弛 :f [ v ] = min ( f [ v ] , max ( f [ u ] , u ) ) f[v] = \min( f[v], \max( f[u], u ) ) f [ v ] = min ( f [ v ] , max ( f [ u ] , u )) 。
我们利用 priority_queue 维护 f f f 值,即可在 O ( M log N ) O(M \log N) O ( M log N ) 内完成预处理。
2.3 判定
合法性判定 :对于一个确定的 k k k ,怎么判断能不能只留下 1 … k 1 \dots k 1 … k ?很简单,只需要满足以下条件即可。
max 1 ≤ i ≤ k { f [ i ] } ≤ k \max_{1 \leq i \leq k}\{f[i]\} \leq k
1 ≤ i ≤ k max { f [ i ]} ≤ k
删除点数量 :接着下一个问题,如果 k k k 合法,我们需要删掉多少个点。
我们必须删掉 v > k v > k v > k 且 f [ v ] ≤ k f[v] \leq k f [ v ] ≤ k 的点,也就是 f [ v ] ≤ k < v f[v] \leq k < v f [ v ] ≤ k < v 。
这就意味着,对于每个顶点 v v v ,它会对 k ∈ [ f [ v ] , v − 1 ] k \in [f[v], v-1] k ∈ [ f [ v ] , v − 1 ] 这个区间上都贡献一次删除。
那么这种区间修改就可以用差分来快速解决了。
2.4 结论
Dijkstra 变体 + 差分思想。
3. 代码实现
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 #include <bits/stdc++.h> using namespace std;using ll = long long ;const int maxn = 3e5 + 5 ;const int inf = 0x3f3f3f3f ; vector< int > adj[maxn];int f[maxn];int dif[maxn];int n, m;struct State { int u, d; bool operator < ( const State &oth ) const { return d > oth.d; } };void dijkstra ( ) { for ( int i = 1 ; i <= n; ++ i ) f[i] = inf; priority_queue< State > pq; pq.push ({ 1 , 1 }); f[1 ] = 1 ; while ( !pq.empty ( ) ) { auto [u, d] = pq.top ( ); pq.pop ( ); if ( d > f[u] ) continue ; for ( int v : adj[u] ) { int nf = max ( d, u ); if ( nf < f[v] ) { f[v] = nf; pq.push ({ v, f[v] }); } } } }void solve ( ) { cin >> n >> m; for ( int i = 1 ; i <= m; ++ i ) { int u, v; cin >> u >> v; adj[u].push_back ( v ); } dijkstra ( ); for ( int i = 1 ; i <= n; ++ i ) { if ( f[i] < i && f[i] != inf ) { dif[f[i]] ++; dif[i] --; } } int sum = 0 ; int max_f = 0 ; for ( int k = 1 ; k <= n; ++ k ) { sum += dif[k]; max_f = max ( max_f, f[k] ); if ( max_f <= k ) cout << sum << '\n' ; else cout << -1 << '\n' ; } }int main ( ) { ios::sync_with_stdio (0 ); cin.tie (0 ); cout.tie (0 ); solve ( ); return 0 ; }
4. 复盘