其他分享
首页 > 其他分享> > # 【圆方树】 $\text{Sol. Luogu P4606}$ 战略游戏

# 【圆方树】 $\text{Sol. Luogu P4606}$ 战略游戏

作者:互联网

\(\large \text{Date: 7.6}\)

\(\text{Sol. Luogu P4606}\) 战略游戏

—— 【圆方树】解法

题目描述

给出一个简单无向连通图。有 \(q\) 次询问:

每次给出一个点集 \(S(2\le |S|\le n)\),问有多少个点 \(u\) 满足 \(u \not\in S\),
且删掉 \(u\) 之后 \(S\) 中的点不全在一个连通分量中。
每个测试点有多组数据。

\(T\le 10^5,\sum |S|\le 2\times10^5,2\le n<m\le10^5\)
时限:\(10s\)

诶,这种题一看题解就知道跟双连通分量有关系。

因为题目只让你删一个点,所以要是两个 \(S\) 中的点处在同一个点双联通子图中,内部的点一定有另一条路径连通,外部的点完全可以找到其他路径实现连通,你是怎么删都没用的。所以,能删的点一定是割点

想到这里,我们可以发现这题是一定要建圆方树的,因为树有许多图没有的算法,处理更方便。

于是套路性地建树——

然后怎么处理呢?我们发现,我们要求的就是 \(S\) 在圆方树上对应的连通子图中的圆点个数减去 \(|S|\),只有删了这些点(割点),才能将他们割裂开。

这里一定要无情地将构出来的圆方树当成一棵树!

怎么求这个使\(S\)中这些点连通的(唯一)点集的大小呢?《算法竞赛进阶指南》的第 \(384\) 页告诉我们,将这些点(\(S\))按照一定顺序(这里就按dfn[x]排序)排好为 \(s_1,s_2,s_3,...,s_n\) ,依次覆盖 \(s_1\to s_2,s_2\to s_3,s_3\to s_4,...s_{n-1}\to s_n,\) \(\bold{s_n\to s_1}\) (都是两个端点)的树上路径所经过的点,此时这些点的连通子图的每一个点刚好被覆盖两边

于是,我们让圆方树中方点的权值为 \(0\),圆点的权值为 \(1\),那“ \(S\) 在圆方树上对应的连通子图中的圆点个数减去 \(|S|\)”就变成了这个连通子图所含点的权值和。而这个权值和已经被我们转换成了 \(s_1,s_2,...s_n\) 这些点相邻两点(\(s_n\) 对 \(s_1\))的路径长(即所经过点的权值和)的和的 \(\dfrac12\)!那我们就只用实现任意两点之间的距离求解即可,倍增 \(LCA\) 随便搞。

然而题解说“如果子图中的深度最浅的节点是圆点,答案还要加上 \(1\),因为我们没有统计到它”,这个也太细了吧...

要加一句 if(QxLca(a[1], a[S]) <= n) ans += 2; (这里 \(+2\) 是因为后面答案在输出时要 \(\div2\) )

\(\text{Code:}\)

#include <bits/stdc++.h>
#define rg register // 加 register 能快不少
using namespace std;
const int N = 3e5 + 5;

inline int read() {
    int x = 0, w = 0; char ch = getchar();
    while(!isdigit(ch)) { w |= ch == 45; ch = getchar(); }
    while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return w ? -x : x;
}

int Task, n, m, Ti, cnt, Q;
int low[N << 1], dfn[N << 1]; // 记得开 2 倍空间
int st[N], topx;
vector <int> Link[N], Lx[N];

void tarjan(int u) { // 建圆方树
    low[u] = dfn[u] = ++Ti;
    st[++topx] = u;
    for(auto v : Link[u]) {
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if(low[v] == dfn[u]) { // 发现点双联通子图
                ++cnt;
                for(rg int x = 0; x != v; --topx) {
                    x = st[topx];
                    Lx[cnt].push_back(x); // 建树
                    Lx[x].push_back(cnt);
                }
                Lx[cnt].push_back(u);
                Lx[u].push_back(cnt);
            }
        }
        else low[u] = min(low[u], dfn[v]);
    }
}

int dep[N << 1], fa[N << 1][20], dis[N << 1];

void dfs(int u, int Fa) { // 预处理,标记 dep, dfn, dis, fa[x][0]
    dfn[u] = ++Ti;
    dep[u] = dep[fa[u][0] = Fa] + 1;
    dis[u] = dis[Fa] + (u <= n ? 1 : 0);
    for(auto v : Lx[u]) if(v != Fa) dfs(v, u);
}

inline void initLCA() { // 倍增递推
    for(rg int j = 1; j <= 18; j++) {
        for(rg int i = 1; i <= cnt; i++) {
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
        }
    }
}

inline int QxLca(int x, int y) { // 查询 LCA
    if(dep[x] < dep[y]) swap(x, y);
    for(rg int i = 18; i >= 0; i--) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
    if(x == y) return x;
    for(rg int i = 18; i >= 0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}

inline void Solve() {
    n = read(); m = read();
    for(rg int i = 1; i <= n; i++) Link[i].clear();
    for(rg int i = 1; i <= 2 * n; i++) Lx[i].clear();
    for(rg int i = 1; i <= n; i++) dfn[i] = low[i] = 0;
    for(rg int i = 1; i <= m; i++) {
        int x = read(), y = read();
        Link[x].push_back(y);
        Link[y].push_back(x);
    }
    cnt = n;
    Ti = 0; tarjan(1); topx--;
    Ti = 0; dfs(1, 0);
    initLCA();
    Q = read();
    while(Q--) {
        rg int S, a[N];
        S = read();
        int ans = -2 * S;
        for(rg int i = 1; i <= S; i++) a[i] = read();
        sort(a + 1, a + S + 1, [](int i, int j) { // 按 dfn 排序
            return dfn[i] < dfn[j];
        });
        for(rg int i = 1; i <= S; i++) {
            int u = a[i], v = a[i % S + 1];
            ans += dis[u] + dis[v] - 2 * dis[QxLca(u, v)]; // 两点距离
        }
        if(QxLca(a[1], a[S]) <= n) ans += 2; // 这个东西非常细!
        printf("%d\n", (ans >> 1)); // 除以 2 输出
    }
}

signed main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("Ans.txt", "w", stdout);
#endif
    Task = read();
    while(Task--) Solve();
    return 0;
}

标签:连通,le,int,P4606,子图,Sol,fa,low,Luogu
来源: https://www.cnblogs.com/Doge297778/p/16452091.html