Loading

HZNU-ACM寒假集训Day9小结 倍增

LCA

   倍增法求最近公共祖先

   首先对于每个结点先进行dfs预处理它的深度,再记录下它们往父亲方向走2的0次,1次...k次步所到达的结点。在这里2的k次大于整棵树的最大深度。

   预处理完后,需要查询两个点u,v的LCA时,先将u,v中深度较大的利用预处理的数组走到和另一个结点相同深度,操作次数不会超过log2|depth(u)-depth(v)|

   接下来从k开始往下枚举,如果u和v往上走2的i次后不同那么它们一起往上走那么多步

   预处理 O(nlogn)查询O(logn)

   不仅如此我们可以动态地给树增加一些叶子结点,在预处理时还可以记录下这段路径权值最大值,最小值或权值和之类的信息。

   Luogu P3379 https://www.luogu.com.cn/problem/P3379

   

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;

struct Edge {
    int t, next;
}e[500010<<1];  //开两倍
int head[500010], tot;

void add_edge(int x, int y) {
    e[++tot].t = y;
    e[tot].next = head[x];
    head[x] = tot;
}

int depth[500010], fa[500001][22], lg[500001];

void dfs(int now, int fath) {         //now表示当前节点,fath表示其父亲节点
    fa[now][0] = fath;
    depth[now] = depth[fath] + 1;
    for (int i = 1; i <= lg[depth[now]]; i++) {
        fa[now][i] = fa[fa[now][i - 1]][i - 1];   //算法核心
    }
    for (int i = head[now]; i; i = e[i].next) {
        if (e[i].t != fath) dfs(e[i].t, now);
    }
}

int LCA(int x, int y) {
    if (depth[x] < depth[y]) swap(x, y);
    while (depth[x] > depth[y])
        x = fa[x][lg[depth[x] - depth[y]] - 1];    //先跳到同一层
    if (x == y) return x;                          //如果x==y那LCA一定就是x
    for (int k = lg[depth[x]] - 1; k >= 0; k--) {  //不断向上跳
        if (fa[x][k] != fa[y][k]) {                //因为要跳到LCA的下一层所以他们肯定不相等,不相等就跳过去
            x = fa[x][k]; 
            y = fa[y][k];
        }
    }
    return fa[x][0];
}

int main() {
    int n, m, s;
    scanf("%d%d%d", &n, &m, &s);
    for (int i = 1; i <= n - 1; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        add_edge(x, y);
        add_edge(y, x);
    }
    for (int i = 1; i <= n; i++) {         //预先算出log2(i)+1的值,用的时候可以直接调用
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }
    dfs(s, 0);
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", LCA(x, y));
    }
    return 0;
}

邻接表版本

 

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;

const int maxn = 500001;
int depth[maxn], fa[maxn][22];
int lg[maxn];
vector<int> e[maxn];
void add_edge(int x, int y) {
    e[x].push_back(y);
}
void dfs(int u, int last) {  //last是u的父亲结点
    depth[u] = depth[last] + 1;
    fa[u][0] = last;
    for (int i = 1; i <=lg[depth[u]]; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for (int i = 0; i < e[u].size(); i++) {
        if (e[u][i] != last) dfs(e[u][i], u);
    }
}
int lca(int x, int y) {
    if (depth[x] < depth[y]) swap(x, y);
    while (depth[x] > depth[y]) {
        x = fa[x][lg[depth[x]-depth[y]]-1];
    }
    if (x == y) return x;
    for (int k = lg[depth[x]]-1; k >= 0; k--) {
        if (fa[x][k] != fa[y][k]) x = fa[x][k], y = fa[y][k];
        //printf("%d\n", fa[x][0]);
    }
    return fa[x][0];
}
int main() {
    int n, m, s, x, y;
    scanf("%d%d%d", &n, &m, &s);
    for (int i = 1; i <= n; i++) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }
    for (int i = 1; i <= n - 1; i++) {
        scanf("%d%d", &x, &y);
        add_edge(x, y);
        add_edge(y, x);
    }
    dfs(s, 0);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &x, &y);
        printf("%d\n", lca(x, y));
    }
    return 0;
}

 

 

 

POJ 1330  (邻接表存储)

  

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
const double PI = acos(-1.0);
typedef long long ll;
using namespace std;


int fa[10005][16], depth[10005], d[10005];
vector<int> e[10005];
void dfs(int u) {
    for (int i = 1; i <= 15; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for (int i = 0; i < e[u].size(); i++) {
        int v = e[u][i];
        depth[v] = depth[u] + 1;
        fa[v][0] = u;
        dfs(v);
    }
}
int lca(int u, int v) {
    if (depth[u] > depth[v]) {
        swap(u, v);
    }
    int t = depth[v] - depth[u];
    for (int i = 15; i >= 0; i--) {
        if (1 << i & t) v = fa[v][i];
    }
    if (u == v) return u;
    for (int i = 15; i >= 0; i--) {
        if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    }
    return fa[u][0];
}
int main() {
    int n, T;
    scanf("%d", &T);
    while (T--) {
        memset(depth, 0, sizeof depth);
        memset(fa, 0, sizeof fa);
        memset(d, 0, sizeof d);
        scanf("%d", &n);
        int u, v;
        for (int i = 1; i < n; i++) {
            scanf("%d%d", &u, &v);
            e[u].push_back(v);
            d[v]++;
        }
        scanf("%d%d", &u, &v);
        int rt = 0;
        for (int i = 1; i <= n; i++) if (!d[i])    rt = i;
        dfs(rt);
        printf("%d\n", lca(u, v));
        for (int i = 1; i <= n; i++) {
            e[i].clear();
        }
    }
    return 0;
}

 CodeForces - 697C Lorenzo Von Matterhorn

 不需要求出LCA   数的移动(二叉树编号)

  

#include<iostream>
#include<map>
using namespace std;
typedef unsigned long long ll;
map<ll,ll> mp; 

int main(){
    int q;
    int f;
    ll l,r,w;
    scanf("%d",&q);
    for(int i=0;i<q;i++){
        scanf("%d",&f);
        if(f==1) {
            scanf("%lld%lld%lld",&l,&r,&w);
            while(l!=r){
                if(l>r) mp[l]+=w,l/=2;
                else mp[r]+=w,r/=2;
            }
        }
        else {
            scanf("%lld%lld",&l,&r);
            ll ans=0;
            while(l!=r){
                if(l>r) ans+=mp[l],l/=2;
                else ans+=mp[r],r/=2;
            }
            printf("%lld\n",ans);
        }
    } 
    return 0;
}

   倍增应用

   

 

   From OI wiki

   

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<cmath>
const double PI = acos(-1.0);
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;

const int mod = 1000000007;

int modadd(int a, int b) {
    if (a + b >= mod) return a + b - mod;   //加快模运算
    return a + b;
}

int vi[1000005];
int go[75][1000005];         //go[i][x]表示第x个点跳2^i步后的终点
int sum[75][1000005];        //sum[i][x]表示第x个点跳2^i步之后能获得的点权和

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &vi[i]);
    }
    for (int i = 1; i <= n; i++) {
        go[0][i] = (i + k) % n + 1;
        sum[0][i] = vi[i];
    }
    int logn = 31 - __builtin_clz(n);   //快捷的取2对数方法
    for (int i = 1; i <= logn; i++) {
        for (int j = 1; j <= n; j++) {
            go[i][j] = go[i - 1][go[i - 1][j]];
            sum[i][j] = modadd(sum[i - 1][j], sum[i - 1][sum[i - 1][j]]);
        }
    }

    ll m;
    scanf("%lld", &m);

    int ans = 0;
    int curx = 1;
    for (int i = 0; m; i++) {
        if (m & (1 << i)) {
            ans = modadd(ans, sum[i][curx]);
            curx = go[i][curx];
            m ^= 1ll << i;     //将第i位置零       //1ll是64位的1
        }
    }
    printf("%d", ans);
    return 0;
}

 

posted @ 2020-02-06 10:56  MQFLLY  阅读(138)  评论(0编辑  收藏  举报