Description陈教授非常喜欢玩电脑游戏。他现在正在游戏中与可怕的怪物战斗。
战场由 n n n 个交叉点组成,编号为 1 到 n n n 。这些交叉点之间有 m m m 条有向边,因此整个战场可以被看作一个有向无环图(DAG) 。玩家现在处在交叉点 1,并拥有 X X X 点生命值(HP)。
除了交叉点 1,每个交叉点上都有一个怪物。当玩家第一次 移动到某个交叉点时,必须与该交叉点上的怪物战斗。在战斗中,玩家会损失 a i a_i a i 点 HP,而在击败怪物后,他会获得 b i b_i b i 点 HP。注意:一旦 HP 变成负数(< 0),游戏就会失败 ,所以必须确保这种情况绝不会发生 。
如果玩家多次访问同一个交叉点,战斗只会在第一次发生——怪物不会复活。玩家只能沿着给定的 m m m 条有向边移动,或者通过魔法飞回交叉点 1 。飞行可以进行多次。
此外,从交叉点 1 总是可以到达任意一个交叉点 。
当所有怪物都被击败后,陈教授就可以通过这一关卡。
请你编写一个程序,计算出通关所需的最小初始生命值 HP(即最小的 X X X ) 。
n + m ≤ 72 , m ≥ n − 1 n+m\leq 72,m\geq n-1 n + m ≤ 7 2 , m ≥ n − 1 。
Solution首先 n ≤ 25 n\leq 25 n ≤ 2 5 时可以暴力状压 dp 做到 O ( 2 n n ) O(2^nn) O ( 2 n n ) 。
对于 n > 25 n>25 n > 2 5 ,m ≤ 72 − n = 46 m\leq 72-n=46 m ≤ 7 2 − n = 4 6 ,此时 m − n m-n m − n 比较小,很容易想到这是 dfs 树上非树边的数量。
注意到操作的过程类似一棵树,即一个点的父亲选了,这个点才能选,所以考虑暴力枚举出每个点的父亲,这是 O ( 2 m − n + 1 ) O(2^{m-n+1}) O ( 2 m − n + 1 ) 级别的。
剩下的部分形如:·给定一棵树,一个点的父亲选了,这个点才能选,问最小初始生命值。
这是个比较经典的贪心问题,先不管父亲的限制,通过排序得到一个最优的顺序。
设最优顺序中第一个是 u u u ,那么当 u u u 的父亲选了后一定贪心地选 u u u ,所以将 u u u 和 f a u fa_u f a u 合并后就转化为了一个规模为 n − 1 n-1 n − 1 的问题。实现时用 set 维护。
具体排序方式为临项交换,分讨一下两个点是当前顺序优还是交换后优即可。
这部分时间复杂度:O ( 2 m − n + 1 n log n ) O(2^{m-n+1}n\log n) O ( 2 m − n + 1 n log n ) 。
Code1 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 105 106 107 108 109 110 111 112 113 #include <bits/stdc++.h> #define int int64_t const int kMaxN = 75 ;int n, m;int a[kMaxN], b[kMaxN], gs[kMaxN];bool g[kMaxN][kMaxN];namespace Sub1 {int f[1 << 25 ];void solve () { std::fill_n (f, 1 << n, 1e18 ); f[1 ] = 1 ; for (int s = 1 ; s < (1 << n); s += 2 ) { int gss = 0 , sum = 0 ; for (int i = 1 ; i <= n; ++i) if (s >> (i - 1 ) & 1 ) gss |= gs[i], sum += b[i] - a[i]; for (int i = 1 ; i <= n; ++i) { if ((gss >> (i - 1 ) & 1 ) && (~s >> (i - 1 ) & 1 )) { int t = (s | (1 << (i - 1 ))); f[t] = std::min (f[t], std::max (f[s], a[i] - sum)); } } } std::cout << f[(1 << n) - 1 ] << '\n' ; } } namespace Sub2 {struct Node { int a, b; friend bool operator <(Node n1, Node n2) { if ((n1. a < n1. b) != (n2. a < n2. b)) return n1. a < n1. b; if (n1. a < n1. b) return n1. a < n2. a; else return n1. b > n2. b; } friend Node operator +(Node n1, Node n2) { static Node ret; ret.a = std::max (n1. a, n1. a - n1. b + n2. a); ret.b = n1. b - n1. a + n2. b - n2. a + ret.a; return ret; } } t[kMaxN];int ans = 1e18 , p[kMaxN], fa[kMaxN];int find (int x) { return x == fa[x] ? x : fa[x] = find (fa[x]); }void unionn (int x, int y) { int fx = find (x), fy = find (y); if (fx != fy) fa[fx] = fy; }void check () { std::set<std::pair<Node, int >> st; t[1 ] = {0 , 0 }, std::iota (fa + 1 , fa + 1 + n, 1 ); for (int i = 2 ; i <= n; ++i) st.emplace (t[i] = {a[i], b[i]}, i); for (int c = 1 ; c <= n - 1 ; ++c) { int i = st.begin ()->second, pp = find (p[i]); st.erase (st.begin ()); fa[i] = pp; if (pp != 1 ) st.erase ({t[pp], pp}); t[pp] = t[pp] + t[i]; if (pp != 1 ) st.emplace (t[pp], pp); } ans = std::min (ans, t[1 ].a); }void dfs (int u) { if (u == n + 1 ) return check (); for (int i = 1 ; i < u; ++i) if (g[i][u]) p[u] = i, dfs (u + 1 ); }void solve () { dfs (2 ); std::cout << ans << '\n' ; } } void dickdreamer () { std::cin >> n >> m; for (int i = 2 ; i <= n; ++i) { std::cin >> a[i] >> b[i]; } for (int i = 1 ; i <= m; ++i) { int u, v; std::cin >> u >> v; g[u][v] = 1 ; if (n <= 25 ) gs[u] |= (1 << (v - 1 )); } if (n <= 25 ) Sub1::solve (); else Sub2::solve (); }int32_t main () {#ifdef ORZXKR freopen ("in.txt" , "r" , stdin); freopen ("out.txt" , "w" , stdout);#endif std::ios::sync_with_stdio (0 ), std::cin.tie (0 ), std::cout.tie (0 ); int T = 1 ; while (T--) dickdreamer (); return 0 ; }