• 【树论 倍增】51nod1709 复杂度分析


    倍增与位运算有很多共性;这题做法有一点像「线段树上二分」和「线段树套二分」的关系。

    给出一棵n个点的树(以1号点为根),定义dep[i]为点i到根路径上点的个数。众所周知,树上最近公共祖先问题可以用倍增算法解决。现在我们需要算出这个算法精确的复杂度。我们定义计算点i和点j最近公共组先的精确复杂度为bit[dep[i]-dep[lca(i,j)]]+bit[dep[j]-dep[lca(i,j)]](bit[i]表示i在二进制表示下有多少个1,lca(i,j)表示点i和点j的最近公共祖先)。为了计算平均所需的复杂度为多少,请你帮忙计算任意两点计算最近公共组先所需复杂度的总和。
    即计算 n1i=1nj=i+1 bit[dep[i]-dep[lca(i,j)]]+bit[dep[j]-dep[lca(i,j)]]

    Input

    第一行一个数n表示点数(1<=n<=100,000)
    接下来n-1行每行两个数x,y表示一条边(1<=x,y<=n)

    Output

    一个数表示答案

    Input示例

    4
    1 2
    1 3
    2 4

    Output示例

    8

    题目分析

    题目已经良心地把要求的式子给出来了。

    自上向下的方法一

    位运算计数题自然而然考虑按位计算贡献,注意到要求的是bit即个数,也就是说高位和低位是同性的。而这题略有特殊的是在于可以与倍增相结合做一些有趣的事情。

    由于边权为1,倍增预处理的到祖先节点的二的幂次,就是到祖先节点的距离的二进制拆分。

    那么就可以先枚举logn数量的每一种2^i深度d,再枚举所有n个点。对于每一个枚举的点,统计与它相距2^{d-1}<dis≤2^d的祖先节点的贡献。

    直观来说就是这张图。主要代码是这样的:

     1     for (int d=0; d<19; d++)
     2     {
     3         memset(w, 0, sizeof w);
     4         for (int ix=1; ix<=n; ix++)
     5         {
     6             int i = chain[ix];    //树的dfs序,因为要自上向下做
     7             w[i] = tot[f[i][d+1]]-tot[f[i][d]];
     8             if (dep[i] > 1<<(d+1)) w[i] += w[fa[f[i][d+1]]];
     9             ans += w[i];
    10         }
    11     }

    如果想要更加程式化的描述,见51Nod1709 复杂度分析这篇博客。

     1 #include<bits/stdc++.h>
     2 const int maxn = 100035;
     3 
     4 int n,w[maxn];
     5 long long ans;
     6 int chain[maxn],tot[maxn],chTot;
     7 int f[maxn][23],dep[maxn],fa[maxn];
     8 int edgeTot,edges[maxn<<1],nxt[maxn<<1],head[maxn];
     9 
    10 int read()
    11 {
    12     char ch = getchar();
    13     int num = 0;
    14     bool fl = 0;
    15     for (; !isdigit(ch); ch = getchar())
    16         if (ch=='-') fl = 1;
    17     for (; isdigit(ch); ch = getchar())
    18         num = (num<<1)+(num<<3)+ch-48;
    19     if (fl) num = -num;
    20     return num;
    21 }
    22 void addedge(int u, int v)
    23 {
    24     edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
    25     edges[++edgeTot] = u, nxt[edgeTot] = head[v], head[v] = edgeTot;
    26 }
    27 void dfs(int x, int fat)
    28 {
    29     f[x][0] = x, f[x][1] = fa[x] = fat, dep[x] = dep[fat]+1;
    30     tot[x] = 1, chain[++chTot] = x;
    31     for (int i=head[x]; i!=-1; i=nxt[i])
    32     {
    33         int v = edges[i];
    34         if (v!=fat){
    35             dfs(v, x);
    36             tot[x] += tot[v];
    37         }
    38     }
    39 }
    40 int main()
    41 {
    42     memset(head, -1, sizeof head);
    43     n = read();
    44     for (int i=1; i<n; i++) addedge(read(), read());
    45     dfs(1, 1);
    46     for (int j=2; j<=19; j++)
    47         for (int i=1; i<=n; i++)
    48             f[i][j] = fa[f[f[i][j-1]][j-1]];
    49     for (int d=0; d<19; d++)
    50     {
    51         memset(w, 0, sizeof w);
    52         for (int ix=1; ix<=n; ix++)
    53         {
    54             int i = chain[ix];
    55             w[i] = tot[f[i][d+1]]-tot[f[i][d]];
    56             if (dep[i] > 1<<(d+1)) w[i] += w[fa[f[i][d+1]]];
    57             ans += w[i];
    58         }
    59     }
    60     printf("%lld
    ",ans);
    61     return 0;
    62 }

    自上向下的方法二

    在这里给出一种O(nlog)的做法。记录每个节点的fa和子树size。
    枚举一个k,用s[i]表示节点i子树内和i距离小于 2^k 大于等于2^k−1的节点数,v[i]表示节点i子树内和i距离小于 2^k 大于等于 2^k−1 的节点和i距离的bit总数,now[i]表示i向上的第 2^k−1 个祖先。
    每次倍增计算每个点子树内和它距离小于 2^k 大于等于 2^k−1 的点的距离对答案的贡献即可。

    @hzq84621  自http://www.51nod.com/question/index.html#!questionId=1546

    【博客园抽风没法正常显示数学公式我也很难受啊】

    END

  • 相关阅读:
    ajax全套
    url设计规范
    内置下划线方法
    rest_framework视图
    rest_framework
    数据库设置
    HDU 6231
    HDU 6242
    CodeForces 546D
    CodeForces 940E
  • 原文地址:https://www.cnblogs.com/antiquality/p/9371369.html
Copyright © 2020-2023  润新知