首页 > 代码库 > [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 树链剖分 最小生成树