首页 > 代码库 > [HDU 3710] Battle over Cities 树链剖分 最小生成树
[HDU 3710] Battle over Cities 树链剖分 最小生成树
题意
给定一张 $N$ 个点 $M$ 条边的无向连通图, 求删除任意点的最小生成树的边权之和.
$0 < N \le 20000$ .
$0 \le M \le 100000$ .
分析
我们会求整张图的最小生成树.
尝试求出来, 然后与我们所求进行联系.
我们发现, 把当前点 $x$ 断开之后, 会形成若干个连通块.
要求将这么多块连通的最小边权之和 $S$ , 答案即为 $原本答案 - 与 x 相连的边权之和 + S$ .
我们考虑对新图进行 Kruskal.
在此之前, 我们要尝试用具体的量来刻画出新图的点和边.
点.
给每个连通块从 $1$ 开始进行标号, 子树外的连通块标号最大.
为了方便地求出每个 $s$ 在哪个标号, 我们需要记 $id[son] = ++ind$ , $son$ 为 $x$ 的后继.
若 $s$ 在子树外, 那么即为最大的 $ind$ .
若 $s$ 在子树内, 通过树链剖分找到比 $dep[x]$ 大的第一个节点 $son$ , $id[son]$ 即为连通块.
横向边.
对于非树边 $(x, y)$ , 则 $x$ 的连通块到 $y$ 的连通块作为横向边的时候, 当且仅当当前删除 $LCA(x,y)$ .
我们先用树剖求出所有非树边的 LCA , 用 vector 存下来.
纵向边.
记 $out[i]$ 为 连通块的根为 $i$ 时 到外面的连边的最小值.
考虑非树边 $(x, y, w)$ 对 $out$ 的影响.
发现路径上除了最上面的三个点, 都与 $out$ 取 $\min$ .
考虑用 树剖 + zkw线段树 + 标记下传 进行预处理 $out$ .
实现
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <vector> using namespace std; #define F(i, a, b) for (register int i = (a); i <= (b); i++) const int N = 20005; const int INF = ~0u>>1; int n, m; struct Edge { int x, y, w; bool tag; friend inline bool operator < (Edge A, Edge B) { return A.w < B.w; } }ed[100005]; int f[N]; int sum, srd[N]; vector<int> g[N]; int pre[N], dep[N], siz[N], son[N]; int top[N], tid[N], pid[N], num; vector<Edge> cross[N]; int mn[70000], out[N]; // int m; int id[N]; inline int rd(void) { int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1; int x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f; } inline void gmin(int &x, int y) { x = min(x, y); } inline int Find(int x) { return f[x] == x ? x : f[x] = Find(f[x]); } void Son(int x) { siz[x] = 1; for (vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++) if (pre[x] != *it) { pre[*it] = x, dep[*it] = dep[x]+1; Son(*it); siz[x] += siz[*it]; if (siz[son[x]] < siz[*it]) son[x] = *it; } } void Split(int x, int anc) { top[x] = anc, pid[ tid[x] = ++num ] = x; if (son[x] > 0) Split(son[x], anc); for (vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++) if (pre[x] != *it && *it != son[x]) Split(*it, *it); } inline int LCA(int x, int y) { while (top[x] != top[y]) dep[top[x]] > dep[top[y]] ? x = pre[top[x]] : y = pre[top[y]]; return dep[x] < dep[y] ? x : y; } inline void Min(int L, int R, int w) { for (L--, R++, L += (1<<m), R += (1<<m); L^R^1; L >>= 1, R >>= 1) { if (!(L&1)) gmin(mn[L^1], w); if (R&1) gmin(mn[R^1], w); } } inline void Fill(int x, int D, int w) { // dep >= D if (dep[x] < D) return; for (int t = top[x]; ; x = pre[t], t = top[x]) if (dep[t] > D) Min(tid[t], tid[x], w); else { Min(tid[x] + (D - dep[x]), tid[x], w); // ? D // tid[x] dep[x] break; } } void Tsunami(int x, int L, int R, int w) { if (R < 1 || L > n) return; gmin(w, mn[x]); if (L == R) { out[pid[L]] = w; return; } int M = (L+R)>>1; Tsunami(x<<1, L, M, w); Tsunami(x<<1|1, M+1, R, w); } inline int Go(int x, int D) { // guaranteed that dep[x] >= D for (int t = top[x]; ; x = pre[t], t = top[x]) if (D >= dep[t]) return pid[tid[x] + (D - dep[x])]; //同上 } int main(void) { #ifndef ONLINE_JUDGE freopen("xsy2441.in", "r", stdin); freopen("xsy2441.out", "w", stdout); #endif for (int T = rd(), t = 1; t <= T; t++) { n = rd(), m = rd(); F(i, 1, m) { int x = rd(), y = rd(), d = rd(), c = rd(); ed[i] = (Edge){x, y, d * (1 - c), false}; } sort(ed+1, ed+m+1); F(i, 1, n) f[i] = i, g[i].clear(); sum = 0, memset(srd, 0, sizeof srd); int c = 1; for (int i = 1; i <= m && c < n; i++) { int x = ed[i].x, y = ed[i].y, w = ed[i].w; int fx = Find(ed[i].x); int fy = Find(ed[i].y); if (fx != fy) { ed[i].tag = true, g[x].push_back(y), g[y].push_back(x); f[fx] = fy; sum += w, srd[x] += w, srd[y] += w, c++; } } memset(pre, 0, sizeof pre), memset(dep, 0, sizeof dep), memset(siz, 0, sizeof siz), memset(son, 0, sizeof son); memset(top, 0, sizeof top), memset(tid, 0, sizeof tid), memset(pid, 0, sizeof pid), num = 0; Son(1); Split(1, 1); F(i, 1, n) cross[i].clear(); F(i, 1, m) if (!ed[i].tag) { int x = ed[i].x, y = ed[i].y, z = LCA(x, y); if (x != z && y != z) cross[z].push_back(ed[i]); } int tmp = m; for (m = 0; (1<<m) - 1 < n+1; m++); //reuse m fill(mn+1, mn+(1<<m)+(1<<m), INF), fill(out+1, out+n+1, INF); F(i, 1, tmp) if (!ed[i].tag) { int x = ed[i].x, y = ed[i].y, w = ed[i].w, z = LCA(x, y); Fill(x, dep[z]+2, w); Fill(y, dep[z]+2, w); } Tsunami(1, 0, (1<<m)-1, INF); memset(id, 0, sizeof id); F(cut, 1, n) { int cnt = 0; for (vector<int>::iterator it = g[cut].begin(); it != g[cut].end(); it++) if (*it != pre[cut]) id[*it] = ++cnt; if (pre[cut] > 0) cnt++; num = 0; // num, edge[] are reused. for (vector<Edge>::iterator it = cross[cut].begin(); it != cross[cut].end(); it++) { int x = it->x, y = it->y, w = it->w; int son_x = Go(x, dep[cut]+1); int son_y = Go(y, dep[cut]+1); ed[++num] = (Edge){id[son_x], id[son_y], w, false}; } if (pre[cut] > 0) for (vector<int>::iterator it = g[cut].begin(); it != g[cut].end(); it++) if (*it != pre[cut] && out[*it] != INF) ed[++num] = (Edge){cnt, id[*it], out[*it], false}; sort(ed+1, ed+num+1); F(i, 1, cnt) f[i] = i; int c = 1, s = 0; for (int i = 1; i <= num && c < cnt; i++) { int x = ed[i].x, y = ed[i].y, w = ed[i].w; int fx = Find(x); int fy = Find(y); if (fx != fy) { f[fx] = fy; s += w, c++; } } if (c < cnt) puts("inf"); else printf("%d\n", sum - srd[cut] + s); } } return 0; }
[HDU 3710] Battle over Cities 树链剖分 最小生成树