ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-06 19:00:41  阅读:145  来源: 互联网

标签:连通 le int P4606 子图 Sol fa low Luogu


\(\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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有