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\n", 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]; //再跳一步就是答案

}

转载于:https://www.cnblogs.com/liuweimingcprogram/p/6155972.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值