• HZNUACM寒假集训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;
    }
  • 相关阅读:
    NOIP 2016 回文日期
    USACO Ski Course Design
    USACO Combination Lock
    USACO 利润Profits
    POJ 3368 Frequent values
    USACO Balanced Lineup
    JDOJ 1065 打倒苏联修正主义
    JDOJ 2174 忠诚
    VIJOS-P1514 天才的记忆
    VIJOS-P1423 最佳路线
  • 原文地址:https://www.cnblogs.com/hznumqf/p/12267910.html
Copyright © 2020-2023  润新知