[洛谷P2495][SDOI2011]消耗战
题目大意:有一棵$n(n\leqslant2.5\times10^5)$个节点的带边权的树,$m$个询问,每次询问给出$k(\sum\limits_{i=1}^mk_i\leqslant5\times10^5)$个点,要求用最小的代价砍断一些边,使得$1$号点与这$k$个点都不连通,输出最小代价
题解:先考虑一个一次$O(n)$的$DP$,可以把所有子树内没有特殊点的点先删去,令$f(i)$表示把他于他子树内所有的特殊点割断所需要的代价,即为$\sum\limits_{v\in son_u}\min\{w,f_v\}$($w$为这条边边权,若$v$为关键点,$f_v=\inf$)。这样的复杂度是$O(n^2)$的,不可以通过。
可以建虚树,一般建一棵虚树的复杂度是$O(k\log_2n)$的,它可以使得建出来的树中只包含最多$2k$个节点。于是就可以通过本题
卡点:最多$2k$个点,$k\leqslant n$,没有开两倍的空间
C++ Code:
#include <algorithm> #include <cctype> #include <cstdio> #include <cstring> namespace std { struct istream { #define M (1 << 25 | 3) char buf[M], *ch = buf - 1; inline istream() { #ifndef ONLINE_JUDGE freopen("input.txt", "r", stdin); #endif fread(buf, 1, M, stdin); } inline istream& operator >> (int &x) { while (isspace(*++ch)); for (x = *ch & 15; isdigit(*++ch); ) x = x * 10 + (*ch & 15); return *this; } #undef M } cin; struct ostream { #define M (1 << 25 | 3) char buf[M], *ch = buf - 1; inline ostream& operator << (int x) { if (!x) {*++ch = '0'; return *this;} static int S[11], *top; top = S; while (x) {*++top = x % 10 ^ 48; x /= 10;} for (; top != S; --top) *++ch = *top; return *this; } inline ostream& operator << (long long x) { if (!x) {*++ch = '0'; return *this;} static int S[20], *top; top = S; while (x) {*++top = x % 10 ^ 48; x /= 10;} for (; top != S; --top) *++ch = *top; return *this; } inline ostream& operator << (const char x) {*++ch = x; return *this;} inline ~ostream() { #ifndef ONLINE_JUDGE freopen("output.txt", "w", stdout); #endif fwrite(buf, 1, ch - buf + 1, stdout); } #undef M } cout; } #define maxn 250010 int head[maxn], cnt; struct Edge { int to, nxt, w; } e[maxn << 1]; inline void addedge(int a, int b, int c) { e[++cnt] = (Edge) {b, head[a], c}; head[a] = cnt; } int in[maxn], out[maxn], idx, dep[maxn]; namespace Tree { #define M 17 int fa[maxn][M + 1], Min[maxn][M + 1]; void dfs(int u) { in[u] = ++idx; for (int i = 1; i <= M; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; Min[u][i] = std::min(Min[u][i - 1], Min[fa[u][i - 1]][i - 1]); } for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != *fa[u]) { *fa[v] = u; *Min[v] = e[i].w; dep[v] = dep[u] + 1; dfs(v); } } out[u] = idx; } inline int LCA(int x, int y) { if (dep[x] < dep[y]) std::swap(x, y); for (int i = dep[x] - dep[y]; i; i &= i - 1) x = fa[x][__builtin_ctz(i)]; if (x == y) return x; for (int i = M; ~i; i--) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return *fa[x]; } const int inf = 0x3f3f3f3f; inline int query(int x, int k) { int res = inf; for (int i = k; i; i &= i - 1) { res = std::min(res, Min[x][__builtin_ctz(i)]); x = fa[x][__builtin_ctz(i)]; } return res; } #undef M } int n, m; namespace Work { const long long inf = 0x3f3f3f3f3f3f3f3f; inline bool cmp(int a, int b) {return in[a] < in[b];} int k; int list[maxn << 1], S[maxn], top; long long f[maxn]; bool imp[maxn]; void dfs(int u) { f[u] = 0; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; dfs(v); f[u] += std::min(f[v], static_cast<long long> (e[i].w)); } if (imp[u]) f[u] = inf, imp[u] = false; head[u] = 0; } void solve() { cnt = 0; std::cin >> k; for (int i = 0; i < k; i++) { std::cin >> list[i]; imp[list[i]] = true; } std::sort(list, list + k, cmp); int tot = k; for (int i = 0; i < k - 1; i++) list[tot++] = Tree::LCA(list[i], list[i + 1]); list[tot++] = 1; tot = (std::sort(list, list + tot, cmp), std::unique(list, list + tot) - list); top = 0; for (int I = 0, i = *list; I < tot; I++, i = list[I]) { while (top && out[S[top]] < in[i]) top--; if (top) addedge(S[top], i, Tree::query(i, dep[i] - dep[S[top]])); S[++top] = i; } dfs(1); std::cout << f[1] << '\n'; } } int main() { std::cin >> n; for (int i = 1, u, v, w; i < n; i++) { std::cin >> u >> v >> w; addedge(u, v, w); addedge(v, u, w); } Tree::dfs(1); std::cin >> m; memset(head, 0, sizeof head); while (m --> 0) Work::solve(); return 0; }