战略游戏 SDOI2018 圆方树 + 树上倍增求点权和

AC通道!

对于这道题, 我们如何才能使得两个点变得不连通呢?

当然是干掉我们路径上必须经过的点,Which is called 割点

而这里也就要利用到我们圆方树的性质。 这些割点就是我们圆方树上的圆点。

于是,我们轻松的想到一个办法: 直接找出所有的圆点不就好了? 然鹅,我们的时间复杂度这样是过不去的。

那么,如何快速求出所有圆点呢? 不妨换一种思路。 对于一个圆方树,如果我们能找到其包含所有点的连通块(当然是最小的,类似于最小生成树的东西),然后将其减掉s,就是我们的圆点的数量。

(画图手摸)

如果我们将所有的点按照dfs序排序(即dfn),然后依次遍历(请自行画图),发现我们将会得到一个联通图。这不就是我们要找的吗!!

那么,如何统计其大小呢? 也很简单。令 val[方点] = 0, val[圆点] = 0,然后倍增求路径点权和即可。

友情tips:不要忘了,LCA会被多次经过,要记得减掉。1号点和s号点的LCA不会被统计到,记得特判其圆点方点,然后加上。

#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define isdigit(c) ((c)>='0'&&(c)<='9')
#define I_WANT_TO_AK_IOI 0

int read(){
    int x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-')s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

struct tu{  /*  “图 ” */
    int v, next;
};
int n, m;

struct node{
    tu t[N << 1];
    int f[N << 1];
    int bian;

    inline void add(int u, int v){  /*双向边*/ 
        t[++bian] = (tu){v, f[u]}, f[u] = bian;
        t[++bian] = (tu){u, f[v]}, f[v] = bian;
        return ;
    }
    /*  调试错误 
    void out(){
        this-> bian; 
        printf("%d
", bian);
        return ;
    }*/
    void clean(){
        bian = 0;
        memset(f, 0, sizeof(f));
        for(int i = 1;i <= (N << 1); i++)
            t[i] = (tu){0, 0};
        return ;
    }
}G, T;

struct bcc{
    int dfn[N << 1], low[N], stac[N], top;
    int bcccnt, id;

    void tarjan(int now){    /*求圆方树*/
        dfn[now] = low[now] = ++id;
        stac[top++] = now;
        for(int i = G.f[now]; i; i = G.t[i].next){
            int v = G.t[i].v;
            if(!dfn[v]){
                tarjan(v);
                low[now] = min(low[now], low[v]);
                if(dfn[now] <= low[v]){
                    bcccnt++;
                    do{
                        T.add(bcccnt, stac[--top]);
                    } while(stac[top] != v);  
                    T.add(bcccnt, now);  /*圆方树建图*/
                }
            }
            else low[now] = min(low[now], dfn[v]);
        }
        return ;
    }

    inline void clean(){
        id = 0;
        top = 0;
        bcccnt = 0;
        memset(dfn, 0, sizeof(dfn));
        memset(low, 0, sizeof(low));
        return ;
    }
} BCC;

namespace LCA{
    int size[N << 1], deth[N << 1], dfn[N << 1], fa[N << 1][22], id = 0;
    int val[N], siz[N];
    void dfs(int now,int father){
        size[now] = 1;
        deth[now] = deth[father] + 1; 
        dfn[now] = ++id;
        fa[now][0] = father;
        val[now] = val[father] + (now <= n);
        for(int i = T.f[now]; i; i = T.t[i].next){
            int v = T.t[i].v;
            if(v != father){
                dfs(v, now);
                siz[now] += siz[v];
            }
        }
        return ;
    }

    void prepare(){  /*倍增预处理*/
        for(int j = 1;j <= 20; j++)
            for(int i = 1;i <= BCC.bcccnt; i++)
                fa[i][j] = fa[fa[i][j-1]][j-1];
        return ;
    }

    int get_lca(int x, int y){  /*倍增求LCA*/
        if(deth[x] < deth[y]) swap(x, y);
        for(int i = 20; ~i; i--){
            if(deth[fa[x][i]] >= deth[y]) x = fa[x][i];
        }
        if(x == y) return x;
        for(int i = 20; ~i; i--){
            if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
        }
        return fa[x][0];
    }

    inline bool cmp(int x, int y){
        return dfn[x] < dfn[y];
    }

    inline void clean(){
        id = 0;
        memset(dfn, 0, sizeof (dfn));
        memset(fa, 0, sizeof(fa));
        memset(val,0, sizeof (val));
        memset(deth, 0, sizeof(deth));
        memset(size, 0, sizeof(size));
        return ;
    }

}

inline void clean(){
    BCC.clean();
    T.clean();
    G.clean();
    LCA::clean();
    return ;
}
int a[N];

int main(){
//  freopen("hh.txt", "r", stdin);
    int test = read();
    while(test--){
        n = read(), m = read();
        for(int i = 1;i <= m; i++){
            int x = read(), y = read();
            G.add(x, y);  /*先建原图*/ 
        }
        BCC.bcccnt = n;
        BCC.tarjan(1);  /*建立圆方树*/ 
        LCA::dfs(1, 0);
        LCA::prepare(); /*处理倍增和路径点权和*/ 
        int q = read();
        while(q--){
            int s = read(), ans = 0;
            for(int i = 1;i <= s; i++) a[i] = read();
            sort(a + 1, a + s + 1, LCA::cmp);  /*按照dfn排序*/ 
            a[s + 1] = a[1];
            for(int i = 1;i <= s; i++){
                int lca = LCA::get_lca(a[i], a[i + 1]);
                ans += LCA::val[a[i]] + LCA::val[a[i + 1]] - (LCA::val[lca] << 1);
            }
            ans >>= 1;
            ans -= s;
            ans += (LCA::get_lca(a[1], a[s]) <= n);
            printf("%d
", ans);
        }
        clean();  /*不要忘了清空呀*/
    }
    return I_WANT_TO_AK_IOI;
}

首道黑题,也是首次尝试大量的代码分段

原文地址:https://www.cnblogs.com/wondering-world/p/13054928.html