• E. Xenia and Tree 分块 + LCA


    http://codeforces.com/contest/342/problem/E

    如果把询问1存起来,每到sqrt(m)的时候再处理一次。

    那么总复杂度就是msqrt(m)的。

    把要变颜色的节点存起来,可以同时一次O(n)的bfs

    然后就是LCA了。LCA需要倍增的做法。这题真的是个好题。。

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    const int maxn = 2e5 + 20;
    int col[maxn];
    struct node {
        int u, v;
        int tonext;
    }e[maxn];
    int first[maxn];
    int num;
    void add(int u, int v) {
        ++num;
        e[num].u = u;
        e[num].v = v;
        e[num].tonext = first[u];
        first[u] = num;
    }
    int tot[maxn];
    int lentot;
    int dp[maxn];
    struct bfsnode {
        int cur, cnt;
        bfsnode(int a, int b) : cur(a), cnt(b) {}
    };
    queue<struct bfsnode>que;
    bool vis[maxn];
    void bfs() {
        memset(vis, false, sizeof vis);
        for (int i = 1; i <= lentot; ++i) {
            que.push(bfsnode(tot[i], 0));
            dp[tot[i]] = 0;
        }
        while (!que.empty()) {
            struct bfsnode t = que.front();
            que.pop();
            for (int i = first[t.cur]; i; i = e[i].tonext) {
                int v = e[i].v;
                if (vis[v]) continue;
                if (dp[v] <= t.cnt + 1) continue;
                vis[v] = true;
                dp[v] = t.cnt + 1;
                que.push(bfsnode(v, t.cnt + 1));
            }
        }
    }
    int ansc[maxn][25], deep[maxn], fa[maxn];
    void init_LCA(int cur) {
        ansc[cur][0] = fa[cur]; //跳1步,那么祖先就是爸爸
        for (int i = 1; i <= 24; ++i) { //倍增思路,递归处理
            ansc[cur][i] = ansc[ansc[cur][i - 1]][i - 1];
        }
        for (int i = first[cur]; i; i = e[i].tonext) {
            int v = e[i].v;
            if (v == fa[cur]) continue;
            fa[v] = cur;
            deep[v] = deep[cur] + 1;
            init_LCA(v);
        }
    }
    int LCA(int x, int y) {
        if (deep[x] < deep[y]) swap(x, y); //需要x是最深的
        for (int i = 24; i >= 0; --i) { //从大到小枚举,因为小的更灵活
            if (deep[ansc[x][i]] >= deep[y]) { //深度相同,走进去就对了。
                x = ansc[x][i];
            }
        }
        if (x == y) return x;
        for (int i = 24; i >= 0; --i) {
            if (ansc[x][i] != ansc[y][i]) { //走到第一个不等的地方,
                x = ansc[x][i];
                y = ansc[y][i];
            }
        }
        return ansc[x][0]; //再跳一步就是答案
    }
    int dis[maxn];
    void dfs(int cur, int step) {
        dis[cur] = min(dis[cur], step);
        for (int i = first[cur]; i; i = e[i].tonext) {
            int v = e[i].v;
            if (vis[v]) continue;
            vis[v] = true;
            dfs(v, step + 1);
        }
    }
    void work() {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n - 1; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u, v);
            add(v, u);
        }
    
        memset(dis, 0x3f, sizeof dis);
        dis[1] = 0;
        vis[1] = true;
        dfs(1, 0);
    
        memset(dp, 0x3f, sizeof dp);
        col[1] = 1;
        tot[++lentot] = 1;
        bfs();
        lentot = 0;
    
    
    
        fa[1] = 1;
        deep[1] = 0;
        init_LCA(1);
    
        int magic = (int)sqrt(m);
        for (int i = 1; i <= m; ++i) {
            int flag, which;
            scanf("%d%d", &flag, &which);
            if (flag == 1) {
                tot[++lentot] = which;
            } else {
                int ans = dp[which];
                for (int i = 1; i <= lentot; ++i) {
                    int haha = LCA(which, tot[i]);
                    ans = min(ans, dis[which] + dis[tot[i]] - 2 * dis[haha]);
                }
                printf("%d
    ", ans);
            }
            if (lentot >= magic) {
                bfs();
                lentot = 0;
            }
        }
    }
    
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
        work();
        return 0;
    }
    View Code

    LCA是用在树中的,图的不行

    LCA[u][v]表示节点u和v的最近公共祖先,也是深度最大祖先,深度越大的话,证明离u和v越近嘛。这个算法基于dfs的回溯和并查集实现。dfs的时候,搜索到叶子节点(没有儿子)的时候,得到LCA[u][u]=u和LCA[u][fa]=fa,然后,返回到他爸爸那里,并查集合并,f[u]=fa;表明u的爸爸是fa,所以这个时候并查集是向左看齐的,merge(u,v),u只能是爸爸。复杂度O(n²)的算法,能求出整棵树的所有LCA[i][j]值。并查集那里有点奇葩,它也用作了标记数组的作用,所以一开始的并查集,全部是0。

    void dfs(int u) {

        f[u] = u; //首先自己是一个集合

        for (int i = first[u]; i; i = e[i].next) {

            int v = e[i].v;

            if (f[v] == 0) {

                dfs(v);

                merge(u, v);

            }

        }

        for (int i = 1; i <= n; i++) { //遍历每一个点

            if (f[i]) { //已经确定过的,就更新LCA

                LCA[u][i] = LCA[i][u] = find(i);

            }

        }

        return ;

    }

    O(n+Q)算法,用邻接表存取所有询问,要询问的再处理即可,注意去重操作。

    void dfs(int u) {

        f[u] = u; //首先自己是一个集合

        for (int i = first[u]; i; i = e[i].next) {

            int v = e[i].v;

            if (f[v] == 0) {

                dfs(v);

                merge(u, v);

            }

        }

        for (int i = first_query[u]; i; i = query[i].next) {

            int v = query[i].v;

            if (f[v]) { //确定过的话,并且有要求查询

                //要求查询的话这个query保存着,first_query[u]就证明有没了

                //因为插边插了两次,这里要去重。用id保存答案即可

                ans[query[i].id] = find(v);

            }

        }

        return ;

    }

    LCA倍增算法。

    设ansc[cur][i]表示从cur这个节点跳2i步到达的祖先是谁。记录深度数组deep[cur]。深度从0开始,然后算LCA的时候就先把他们弄到同一深度,然后一起倍增。

    Hint:ans[root][3]是自己,都是root。开始的时候fa[root] = root。deep[root] = 0;

    一般这课树是双向的,因为可能结合bfs来做题,所以需要判断不能走到爸爸那里。

    int ansc[maxn][25], deep[maxn], fa[maxn];

    void init_LCA(int cur) {

        ansc[cur][0] = fa[cur]; //跳1步,那么祖先就是爸爸

        for (int i = 1; i <= 24; ++i) { //倍增思路,递归处理

            ansc[cur][i] = ansc[ansc[cur][i - 1]][i - 1];

        }

        for (int i = first[cur]; i; i = e[i].tonext) {

            int v = e[i].v;

            if (v == fa[cur]) continue;

            fa[v] = cur;

            deep[v] = deep[cur] + 1;

            init_LCA(v);

        }

    }

    int LCA(int x, int y) {

        if (deep[x] < deep[y]) swap(x, y); //需要x是最深的

        for (int i = 24; i >= 0; --i) { //从大到小枚举,因为小的更灵活

            if (deep[ansc[x][i]] >= deep[y]) { //深度相同,走进去就对了。

                x = ansc[x][i];

            }

        }

        if (x == y) return x;

        for (int i = 24; i >= 0; --i) {

            if (ansc[x][i] != ansc[y][i]) { //走到第一个不等的地方,

                x = ansc[x][i];

                y = ansc[y][i];

            }

        }

        return ansc[x][0]; //再跳一步就是答案

    }

  • 相关阅读:
    vs.net 2005, 没有找到MSVCR80D.dll的完美解决方案
    C++内存布局从一个修改私有变量的问题想到的
    堆栈详解
    加载.x文件
    深入分析规则引擎
    高级着色语言HLSL入门(5)
    字符数组,字符指针,Sizeof总结
    C++ 隐式和显式 初始化,类型转换
    fread()和fwrite()函数分析
    结构体 对齐的问题
  • 原文地址:https://www.cnblogs.com/liuweimingcprogram/p/6155972.html
Copyright © 2020-2023  润新知