[NOIP2012提高组]疫情控制
题意:在一棵树上,每条边有距离,有若干个军队,每秒走一单位距离,可以往各个节点移动,但最后不能停在1号节点。求覆盖所有叶节点的最短时间是多少。
为了优化大家的阅读感受,先放题解标程,再放解题过程&部分分
首先,军队一定是往根节点走,这样可以使覆盖的叶节点尽可能多,所以要贪心。要使时间最小,就要二分时间再验证答案。
这题本人觉得细节贼多,以至于被连续坑。譬如:有的军队还必须要经过根节点1,再到1的一些子节点。关键如何判断还是个问题。
本题代码如果看不懂没关系(码力欠缺,代码冗长),下面有详细证明。
代码
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 using namespace std; 7 8 #define re register 9 #define LL long long 10 #define rep(i, x, y) for (register int i = x; i <= y; ++i) 11 #define repd(i, x, y) for (register int i = x; i >= y; --i) 12 #define maxx(a, b) a = max(a, b) 13 #define minn(a, b) a = min(a, b) 14 #define inf 1e9 15 #define linf 1e14 16 17 inline int read() { 18 int w = 0, f = 1; char c = getchar(); 19 while (!isdigit(c)) f = c == '-' ? -1 : f, c = getchar(); 20 while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ 48), c = getchar(); 21 return w * f; 22 } 23 24 const int maxn = 5e4 + 5; 25 26 int n, m; 27 28 struct Edge { 29 int u, v, w, pre; 30 }; 31 32 struct Gph { 33 Edge edges[maxn << 1]; 34 int G[maxn], n, m; 35 void init(int n) { 36 this->n = n; 37 m = 0; 38 memset(G, 0, sizeof(G)); 39 } 40 void AddEdge(int u, int v, int w) { 41 edges[++m] = (Edge){u, v, w, G[u]}; 42 G[u] = m; 43 } 44 } G; 45 46 struct Node { 47 int d, p; 48 } a[maxn], root[maxn]; 49 50 int cnt = 0, isarmy[maxn], label[maxn]; 51 LL minT[maxn], dist[maxn]; 52 53 bool cmp1(Node a, Node b) { 54 return a.d < b.d; 55 } 56 57 bool cmp2(Node a, Node b) { 58 return a.d > b.d; 59 } 60 61 int vis[maxn], cov[maxn]; 62 63 struct Tree { 64 int lab; 65 void dfs(int u) { 66 if (isarmy[u]) minT[u] = 0; 67 else minT[u] = linf; 68 label[u] = lab; 69 vis[u] = 1; 70 for (re int i = G.G[u]; i; i = G.edges[i].pre) { 71 Edge &e = G.edges[i]; 72 if (vis[e.v]) continue; 73 dist[e.v] = dist[u] + e.w; 74 dfs(e.v); 75 minn(minT[u], minT[e.v] + e.w); 76 } 77 } 78 void build() { 79 memset(vis, 0, sizeof(vis)); 80 vis[1] = 1; 81 for (re int i = G.G[1]; i; i = G.edges[i].pre) { 82 Edge &e = G.edges[i]; 83 lab = e.v; 84 root[++cnt].p = e.v; 85 root[cnt].d = e.w; 86 dist[e.v] = e.w; 87 dfs(e.v); 88 } 89 } 90 void dfs_root(int u, int TIME) { 91 vis[u] = 1; 92 if (cov[u]) { 93 minT[u] = 0; 94 return; 95 } else minT[u] = linf; 96 cov[u] = 1; 97 bool flag = 1; 98 for (re int i = G.G[u]; i; i = G.edges[i].pre) { 99 Edge &e = G.edges[i]; 100 if (vis[e.v]) continue; 101 flag = 0; 102 dfs_root(e.v, TIME); 103 cov[u] &= cov[e.v]; 104 minn(minT[u], minT[e.v] + e.w); 105 } 106 if (flag) cov[u] = 0; 107 if (minT[u] <= TIME) cov[u] = 1; 108 } 109 } T; 110 111 bool check(LL TIME) { 112 int g[maxn], g_cnt = 0, h[maxn], use[maxn], t = 0; 113 memset(g, 0, sizeof(g)); 114 memset(h, 0, sizeof(h)); 115 memset(cov, 0, sizeof(cov)); 116 memset(vis, 0, sizeof(vis)); 117 memset(use, 0, sizeof(use)); 118 vis[1] = 1; 119 rep(i, 1, m) 120 if (a[i].d <= TIME) h[label[a[i].p]] = i, t = i; else cov[a[i].p] = 1; 121 122 int g_p = 0; 123 use[0] = 1; 124 rep(i, 1, cnt) { 125 T.dfs_root(root[i].p, TIME); 126 if (cov[root[i].p]) continue; 127 if (use[h[root[i].p]]) { 128 for (g_p++; g_p <= t && (a[g_p].d + root[i].d > TIME || use[g_p]); g_p++) ; 129 if (g_p > t) return false; 130 use[g_p] = 1; 131 } else use[h[root[i].p]] = 1; 132 } 133 return g_p <= t ? 1 : 0; 134 } 135 136 int main() { 137 n = read(); 138 139 rep(i, 1, n-1) { 140 re int u = read(), v = read(), w = read(); 141 G.AddEdge(u, v, w); 142 G.AddEdge(v, u, w); 143 } 144 145 m = read(); 146 147 rep(i, 1, m) a[i].p = read(), isarmy[a[i].p] = 1; 148 149 T.build(); 150 151 rep(i, 1, m) a[i].d = dist[a[i].p]; 152 153 sort(a + 1, a + m + 1, cmp1); 154 sort(root + 1, root + cnt + 1, cmp2); 155 156 LL l = 0, r = linf; 157 while (l < r) { 158 LL mid = l + r >> 1; 159 if (check(mid)) r = mid; 160 else l = mid+1; 161 } 162 163 if (l == linf) printf("-1"); 164 else printf("%lld", l); 165 166 return 0; 167 }
题解
上面已经说过,想要覆盖更多叶节点,必须往根走。因为不能停在1号节点,暂时不考虑军队过首都到别的地方,问题可以看成:1号节点下有若干节点,只要判断每一个节点的子树的叶节点是否被覆盖即可。
先做些记号:
$a[i] = \{dist,\ p\}$ $a$是$Army$军队的缩写,其中有两个量:$dist$表示该军队到根节点的距离,$p$表示该军队初始所在的节点。
$root[i] = \{dist,\ p\}$ 与$a$类似,每一个表示的是1号节点的子节点到1号点的距离和该子节点的编号。等于单独把子节点提出来当作若干根节点,之后统一称作$root$。
$cnt$ 表示$root$的大小。
$cov[i]$ 表示第$i$个节点是否被军队覆盖,1表示被覆盖。 请注意:这个$cov$是会变动的(之后你就知道了)
$minT[i]$ 表示在覆盖情况为$cov$的情况下,第$i$个节点被覆盖的最小时间。
二分$Ans$,再判断。如何判断?有一种方法是通过倍增,每次将这$n$个军队提前跳,再dfs判断是否覆盖。当然也行。不过我这里用的是更高效的方法。将所有的$root$ dfs一遍,求$minT$并通过$minT$来判断是否覆盖。不难可以得出:$minT[u]=minT[v]+w[u,v]\ |\ v\in u$的子节点,初始值:当$cov[u]=1$时,$minT[u]=0$,否则$minT[u]=INF$。判断覆盖的话,如果$minT[u]\leq Ans$,则说明一定有军队到这个点,即$u$点以下的叶节点全部能被覆盖,或者递归$u$全部的子节点,如果全满足时,也可以全部覆盖,否则就不行。
加上复杂的情况:某些$root$子树中不存在军队。
怎么办?
从别的地方调呗!
最后判断一下即可。