[POI 2009] gas 贪心
GAS gastask
给出一个n个节点的树,现在要在这棵树上放置一些指示物:
1. 一个节点可以放置多个指示物;
2. 一个指示物拥有一个笼罩范围,即与它本身所在节点的距离在k个节点之内的任何节点,它都可以选择笼罩
3. 每个指示物最多笼罩s个不同的节点。
问题是使用最少的指示物将整棵树全部笼罩到。
Solution
题目给出了一棵树,可以比较容易地得到对于一个节点,我们找到一个从它这里可以覆盖到的最远的子节点,那么从它到这个子节点的路径上的每一个节点设立指示物都可以覆盖到这个子节点,那么从当前的这个节点设立应该是最优的,这样有没有可能有反例呢?
我们假设这条路径中有某一个节点i的子树中有另一个节点j,这个节点是i可以到达而之前的那个祖先节点所无法到达的,那么这个时候就有可能出现在节点i设立指示物比在祖先节点设立更优的情况(想想就知道),这就出现了反例。
为了避免这种情况,我们可以每次找深度最大的一个叶子节点,然后再找离它最远的祖先节点作为覆盖它的节点,那么就可以保证这样的决策不会出现上面的反例。
由于最深的节点满足拓扑序,所以可以使用一个队列来维护,对于每一个节点查询覆盖节点。整个算法的复杂度为O(nk)。
不过这样实现起来比较麻烦,这里我学到了一个来自涛哥的方法:我们使用f[i][j]表示从i号节点向子树中深搜j步所能到达的节点个数,g[i][j]表示从i号向子树中深搜j步所能到达的节点现在设立的指示物还能覆盖多少节点(有点绕)。
我们先深度遍历每一个点,然后递归处理出每个点能覆盖它的子树的节点个数最大是多少,由于这样是最优的所以我们在这个节点设立相应的指示物来覆盖它们,同时更新还能覆盖的节点个数,然后我们还要更新覆盖点和被覆盖点都在当前节点子树中的情况,即跨立的情况,如果子树中有某一个节点可以到达子树中的另外一个点并且其当先设立的指示物还能继续覆盖点,那么就把这些点覆盖。这样深搜完之后,再在外面做一遍非最优的决策看还有什么没有被调整到的。
#include <stdio.h> #include <memory.h> const int nmax = 100000, kmax = 20; int n, k, s, f[nmax + 18][kmax + 18], g[nmax + 18][kmax + 18]; int fst[nmax + 18], pnt[(nmax << 1)+ 18], nxt[(nmax << 1)+ 18], tot = 0, ans = 0; bool ed[nmax + 18]; void addedge (int s, int t) { pnt[++tot] = t; nxt[tot] = fst[s]; fst[s] = tot; pnt[++tot] = s; nxt[tot] = fst[t]; fst[t] = tot; } int top (int x) { return x ? (x - 1) / s + 1 : 0; } void adjust (int &a, int &b) { if (a >= b) a -= b, b = 0; else b -= a, a = 0; } void dfs (int i) { ed[i] = true; for (int kk = fst[i], j = pnt[kk]; kk; kk = nxt[kk], j = pnt[kk]) if (!ed[j]) { dfs (j); for (int l = 1; l <= k; ++l) { f[i][l] += f[j][l - 1]; g[i][l] += g[j][l - 1]; g[i][l] <?= n; } } ans += top (f[i][k]); f[i][0] = 1; g[i][0] = top (f[i][k]) * s; for (int j = k; j >= 0; --j) { adjust (f[i][k - j], g[i][j]); if (j < k) adjust (f[i][k - j - 1], g[i][j]); } } int main () { freopen ("gas.in", "r", stdin); freopen ("gas.out", "w", stdout); scanf ("%d%d%d", &n, &s, &k); for (int i = 1, s, t; i < n; ++i) scanf ("%d%d", &s, &t), addedge (s, t); dfs (1); for (int i = k; i >= 0; --i) for (int j = k - i; j >= 0; --j) adjust (f[1][j], g[1][i]); int S = 0; for (int i = 0; i <= k; ++i) S += f[1][i]; printf ("%d", ans + top (S)); return 0; }