[BZOJ5329][Sdoi2018]战略游戏(圆方树+虚树)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=5329

Solution

首先介绍一下圆方树。
先求点双连通分量,设原图中的点为圆点。
然后对于每个点双连通分量建一个方点,从方点向连通分量内的所有点连一条边,并把这个连通分量里原有的边删掉。
在 Tarjan 求点双的过程中,所有点分别入栈了一次,而除了根之外所有的点都出栈了一次,设有 m m 个点双,那么除了出栈过的 n1 个点进入点双之外,每次找到连通分量时还要把一个割点算入,这样所有点双的点数之和为 n+m1 n + m − 1 ,而上面的构图中圆点和方点的总数为 n+m n + m ,所以得出,这样得到的是一棵树,这棵树就是圆方树。
圆方树有一些神奇的性质:
(1)原图的割点对应圆方树的割点(方点除外),也就是属于多个方点的圆点。
(2)两个点 u,v u , v 之间的割点集合为圆方树 u u v 的路径上除 u u v 的所有圆点。
回到问题。建出圆方树,可以把问题进行转化。
求一个点集之间两两的路径的并上,有多少个圆点(这个选出的点集除外)。
点集之间两两的路径并实际上就是这个点集构成的虚树。
把关键点按 DFS 序排序后我们要求的就是求相邻两个点之间路径的并。
我们给每条边一个权值: (u,v) ( u , v ) 边,如果深度较大的点 v v 为圆点则 (u,v) 的权值为 1 1 ,否则为 0
这样,问题再次转化:
求点集 S S 所在虚树的边权和,减去 |S|如果虚树的根为圆点则加一
容易想到,点集 S S 的虚树的边权和,
就是 DFS 序排序之后 1 2 2 2 3 3 ,…, |S|1 S S S 1 1 的路径边权之和除以 2
利用倍增 LCA 即可求得。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Tree(u) for (int e = adj2[u], v; e; e = nxt2[e]) if ((v = go2[e]) != fu)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = N << 1, L = M << 1, LogN = 20;
int n, m, q, ecnt, nxt[L], adj[N], go[L], T, dfn[N], low[N], top,
stk[N], nm, ecnt2, nxt2[L], adj2[M], go2[L], dep[M], fa[M][LogN],
vn, vir[N], pos[M], QAQ, dis[M];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void add_edge2(int u, int v) {
    nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
    nxt2[++ecnt2] = adj2[v]; adj2[v] = ecnt2; go2[ecnt2] = u;
}
void dfs(int u) {
    dfn[stk[++top] = u] = low[u] = ++T;
    Edge(u)
        if (!dfn[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
            if (dfn[u] <= low[v]) {
                nm++;
                while (stk[top] != v) add_edge2(nm, stk[top--]);
                add_edge2(nm, v); top--;
                add_edge2(nm, u);
            }
        }
        else low[u] = min(low[u], dfn[v]);
}
void dfsLCA(int u, int fu) {
    int i;
    pos[u] = ++QAQ;
    dis[u] = u == 1 ? 0 : dis[fu] + (u <= n);
    dep[u] = dep[fa[u][0] = fu] + 1;
    For (i, 0, 17) fa[u][i + 1] = fa[fa[u][i]][i];
    Tree(u) dfsLCA(v, u);
}
int lca(int u, int v) {
    int i;
    if (dep[u] < dep[v]) swap(u, v);
    Rof (i, 18, 0) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    Rof (i, 18, 0) if (fa[u][i] != fa[v][i])
        u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
bool comp(int a, int b) {
    return pos[a] < pos[b];
}
int solve() {
    int i, sum = 0, rot = vir[1];
    sort(vir + 1, vir + vn + 1, comp);
    For (i, 1, vn) {
        int u = vir[i], v = vir[i == vn ? 1 : i + 1], w;
        w = lca(u, v);
        if (dep[w] < dep[rot]) rot = w;
        sum += dis[u] + dis[v] - (dis[w] << 1);
    }
    return (sum >> 1) + (rot <= n) - vn;
}
void work() {
    ecnt = T = top = ecnt2 = QAQ = 0;
    int i, x, y;
    n = nm = read(); m = read();
    For (i, 1, n) adj[i] = dfn[i] = 0;
    For (i, 1, (n << 1)) adj2[i] = 0;
    For (i, 1, m) x = read(), y = read(), add_edge(x, y);
    dfs(1); dfsLCA(1, 0);
    q = read();
    while (q--) {
        vn = read();
        For (i, 1, vn) vir[i] = read();
        printf("%d\n", solve());
    }
}
int main() {
    int T = read();
    while (T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值