[SDOI2018]战略游戏 圆方树,树链剖分

[SDOI2018]战略游戏

这题是道路相遇题解)的升级版,询问的两个点变成了\(S\)个点。

LG传送门

还是先建出圆方树,考虑对于询问的\(S\)个点,答案就是圆方树上能包含这些点的最小连通块中的圆点个数减去\(S\)。问题变成了怎样求这样的连通块中的圆点个数,直接给结论吧:先搞出树的dfs序,把询问的点按dfs序从小到大排一遍序,每次把答案加上第\(i\)和第\(i + 1\)个点之间的圆点个数,但是不算lca,再加上第\(1\)个和第\(S\)个点之间的圆点个数,然后除以二就得到了这个连通块内不包括整个连通块的lca的圆点个数,可以证明这个连通块内除了lca的所有点都被算了两次,最后判断一下lca是不是圆点,减去\(S\)就是答案。

实测树剖比倍增快很多。

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define R register
#define I inline
#define B 10000000
using namespace std;
const int N = 400003;
char buf[B], *p1, *p2;
I char gc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++; }
I int rd() {
    R int f = 0; R char c = gc();
    while (c < 48 || c > 57)
        c = gc();
    while (c > 47 && c < 58)
        f = f * 10 + (c ^ 48), c = gc();
    return f;
}
int h[N], H[N], sta[N], dfn[N], low[N], vis[N], fa[N], dep[N], siz[N], son[N], top[N], dis[N], q[N], n, c, tim, cnt, stp;
struct edge { int s, g; }e[N], E[N];
I void add(int x, int y) { e[++c] = (edge){h[x], y}, h[x] = c; }
I void Add(int x, int y) { E[++c] = (edge){H[x], y}, H[x] = c; }
I int min(int x, int y) { return x < y ? x : y; }
I int cmp(int x, int y) { return dfn[x] < dfn[y]; }
void dfs(int x) {
    vis[sta[++stp] = x] = 1, dfn[x] = low[x] = ++tim;
    for (R int i = h[x], y, z; i; i = e[i].s)
        if (!dfn[y = e[i].g]) {
            dfs(y), low[x] = min(low[x], low[y]);
            if (low[y] >= dfn[x]) {
                Add(++cnt, x), Add(x, cnt);
                do {
                    vis[z = sta[stp--]] = 0, Add(cnt, z), Add(z, cnt);
                } while (z ^ y);
            }
        }
        else
            low[x] = min(low[x], dfn[y]);
}
void dfs1(int x, int f) {
    fa[x] = f, dep[x] = dep[f] + 1, siz[x] = 1, dis[x] = dis[f] + (x <= n);
    for (R int i = H[x], y, m = 0; i; i = E[i].s)
        if ((y = E[i].g) ^ f) {
            dfs1(y, x), siz[x] += siz[y];
            if (siz[y] > m)
                m = siz[x], son[x] = y;
        }
}
void dfs2(int x, int r) {
    dfn[x] = ++tim, top[x] = r;
    if (son[x])
        dfs2(son[x], r);
    for (R int i = H[x], y; i; i = E[i].s)
        if ((y = E[i].g) ^ fa[x] && y ^ son[x])
            dfs2(y, y);
}
I int lca(int x, int y) {
    while (top[x] ^ top[y])
        dep[top[x]] > dep[top[y]] ? x = fa[top[x]] : y = fa[top[y]];
    return dep[x] < dep[y] ? x : y;
}
I int query(int x, int y) { return dis[x] + dis[y] - (dis[lca(x, y)] << 1); }
int main() {
    R int T = rd(), m, Q, S, i, x, y, ans;
    while (T--) {
        memset(h, 0, sizeof h), memset(H, 0, sizeof H), memset(son, 0, sizeof son), memset(dfn, 0, sizeof dfn);
        cnt = n = rd(), m = rd(), c = 0;
        for (i = 1; i <= m; ++i)
            x = rd(), y = rd(), add(x, y), add(y, x);
        c = tim = stp = 0, dfs(1), tim = 0, dfs1(1, 0), dfs2(1, 1), Q = rd();
        while (Q--) {
            S = rd();
            for (i = 1; i <= S; ++i)
                q[i] = rd();
            sort(q + 1, q + S + 1, cmp), ans = query(q[1], q[S]);
            for (i = 2; i <= S; ++i)
                ans += query(q[i - 1], q[i]);
            printf("%d\n", (ans >> 1) - S + (lca(q[1], q[S]) <= n));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/cj-chd/p/10294681.html

时间: 2024-10-09 18:22:00

[SDOI2018]战略游戏 圆方树,树链剖分的相关文章

bzoj 5329 [SDOI2018] 战略游戏

bzoj 5329 [SDOI2018] 战略游戏 Link Solution 很容易想到虚树 然后发现是一个图... 现学圆方树,套上去,做完了(模板题?) 就是直接上广义圆方树先把这玩意转换成一棵树,然后对当前询问建立虚树,断掉虚树里任何一个点都合法(包括不出现的点,指那些在某个点和其虚树上父亲之间的点),统计一下即可 Code // Copyright lzt #include<stdio.h> #include<cstring> #include<cstdlib>

[SDOI2018]战略游戏

题目描述 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战略游戏的地图由n个城市以及m条连接这些城市的双向道路构成,并且从任意一个城市出发总能沿着道路走到 任意其他城市.现在小C已经占领了其中至少两个城市,小Q可以摧毁一个小C没占领的城市,同时摧毁所有连接这 个城市的道路.只要在摧毁这个城市之后能够找到某两个小C占领的城市u和v,使得从u出发沿着道路无论如何都不 能走到v,那么小Q就能赢下这一局游戏. 小Q和小C一共进行了q局游戏,每一局游戏会给出小C占领

[bzoj5329][Sdoi2018]战略游戏

题目大意:多组数据,每组数据给一张图,多组询问,每个询问给一个点集,要求删除一个点,使得至少点集中的两个点互不连通,输出方案数 题解:圆方树,发现使得两个点不连通的方案数就是它们路径上的原点个数.如何处理重复?可以按圆方树的$dfn$序排序,相邻两点求一下贡献,这样贡献就被重复计算了两次,除去$k$个询问点就行了.还有每次计算中$lca$没有被统计,发现排序后第一个点和最后一个点的$lca$一定是深度最浅的,所以只有这个点没有被统计答案,加上即可 卡点:1.圆方树$dfn$数组没赋值 2.$LC

luogu P3345 [ZJOI2015]幻想乡战略游戏(点分树)

题意自己看... 思路 没想到今(昨)天刷着刷着点分治的水题,就刷出来了一个点分树... 然后就疯狂地找题解,代码,最后终于把它给弄懂了. 点分树--动态点分治,对于此题来说,我们发现设u为当前的补给站位置,v是它的一个儿子.同时设dis(i,j)为树上i点到j点的距离.sumi为以i为跟的子树中d(也就是军队数)的总量 我们把补给站从u转移到v,答案的变化为dis(u,v)*(sumu-sumv)-dis(u,v)*sumv=dis(u,v)*(sumu-2*sumv) 所以当2*sumv>s

CF487E Tourists 圆方树、树链剖分

传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就是一个反例 上面这个图如果缩点双会缩成\(3\)个,但是缩边双会将整个图缩成\(1\)个点. 假如我们询问的是\((1,4)\)之间的简单路径,而图中权值最小的点为\(7\)号点,那么如果缩成了边双联通分量,你的答案会是\(7\)号点的权值,意即认为可以走到\(7\)号点,但实际上如果到\(7\)号

【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)

https://gmoj.net/senior/#contest/show/2989/2 思考什么时候先手会赢. 一开始双方都不会希望走到直径的端点上,因为那样对方就可以走直径而使自己输掉. 删掉直径的端点,考虑剩下的树的子问题. 如果又走到端点去了,对面就走到另外一个端点,那我就走到下一层的直径端点去了. 所以大家一直都不想走到直径端点. 一直删,如果最后只剩1一个点,说明先手必败,否则先手必胜. 如果是一条链,就是链的两边的长度不等先手就必胜. 如果是一棵树,考虑随便找一条直径,每次删去它的

【loj#2524】【bzoj5303】 [Haoi2018]反色游戏(圆方树)

题目传送门:loj bzoj 题意中的游戏方案可以转化为一个异或方程组的解,将边作为变量,点作为方程,因此若方程有解,方程的解的方案数就是2的自由元个数次方.我们观察一下方程,就可以发现自由元数量=边数-点数+连通块数,或者换句话说,若对原图的每个联通块指定一棵生成树,那么确定了生成树之外的边是否进行操作,那么生成树内的边的操作方案就是一定存在并唯一确定的. 那么我们就只需要判断一下什么样的图无解.我们发现每对一条边进行操作,原图内的黑点数量奇偶性不变,那么我们只需判断图中的是否存在某个联通块有

Tourists——圆方树

CF487E Tourists 一般图,带修求所有简单路径代价. 简单路径,不能经过同一个点两次,那么每个V-DCC出去就不能再回来了. 所以可以圆方树,然后方点维护一下V-DCC内的最小值. 那么,从任意一个割点进入这个DCC,必然可以绕一圈再从另一个割点出去. 所以,路径上的最小值,就是圆方树路径上的最小值.方点的最小值就是在这个DCC中走一走得到的. 树链剖分+线段树维护路径 用堆维护方点四周的圆点的最小值.然后更新. 一个问题是: 更新一个割点圆点,会影响到四周所有的方点.暴力更新,菊花

圆方树总结

圆方树:一种将由图转化而成的树,从而大大了增加题目的可解性,且大多广泛用于仙人掌图中. 针对仙人掌图上的圆方树:仙人掌是指一条边至多只被一个环包含的无向图. 树上的点:圆方树上分为两类点,一类是圆点,一类是方点.圆点即原图中所有的点,方点即为了去环而新添加进去的,满足一定性质的点. 构造思路:圆圆边直接加入,对于仙人掌中的任意一个环,每个环上的点在圆方树上对应的圆点向这个环对应的方点连边,方点为一个新建节点. 环的根:指定一个圆点为圆方树的根,把方点的父亲叫做这个方点对应的环的根. 圆方边边权: