树上倍增解析(转载)
来源:互联网 发布:单身约会软件 编辑:程序博客网 时间:2024/05/17 16:53
最近做了一些树上的练习题,发现倍增真的是一种处理树上问题的神奇、方便的方法。
我以前一直打树链剖分打得多,但是学了倍增之后就再也不想打树链剖分了(当然有些题目不得不打)。
倍增比起树链剖分,代码短,容易查错,时空复杂度也优很多(nlogn),只是功能有些欠缺。
倍增的思想是二进制。
首先开一个n×logn的数组,比如fa[n][logn],其中fa[i][j]表示i节点的第2^j个父亲是谁。
然后,我们会发现有这么一个性质:
fa[i][j]=fa[fa[i][j-1]][j-1]
用文字叙述为:i的第2^j个父亲 是i的第2^(j-1)个父亲的第2^(j-1)个父亲。
这是不是很神奇?这样,本来我们求i的第k个父亲的复杂度是O(k),现在复杂度变成了O(logk)。
我们知道,一个数的二进制形式中,如果右边数第i位上是1,表示这个数如果分解为若干个2的次幂的和的形式,其中有一项一定是2^(i-1)。举个例子:10的二进制表示为1010,它的第2位和第4位上是1,所以10=2^1+2^3。
下面是求i的第k个父亲的代码段:
int father(int i,int k) { for(int x=0;x<=int(log2(k));x++) if((1<<x)&k) //(1<<x)&k可以判断k的二进制表示中,<strong>第(x-1)位上</strong>是否为1 i=fa[i][x]; //把i往上提 return i; }
这样讲应该很容易理解吧?
我们可以通过一次dfs处理出fa数组:(dep[i]表示i的深度,这个可以一起处理出来,以后要用)
如果待处理的树有n个节点,那么最多有一个节点会有2^(logn)个父亲,所以我们的fa数组第二维开logn就够了。
这里用max0表示logn。初始化fa为0,若fa[i][j]=0表示i没有第2^j个父亲。
void dfs(int x) { for(int i=1;i<=max0;i++) if(fa[x][i-1]) //在dfs(x)之前,x的父辈们的fa数组都已经计算完毕,所以可以用来计算x fa[x][i]=fa[fa[x][i-1]][i-1]; else break; //如果x已经没有第2^(i-1)个父亲了,那么也不会有更远的父亲,直接break for(/*每一个与x相连的节点i*/) if(i!=fa[x][0]) //如果i不是x的父亲就是x的儿子 { fa[i][0]=x; //记录儿子的第一个父亲是x dep[i]=dep[x]+1; //处理深度 dfs(i); } }
这样,我们在nlogn的时间内可以通过一遍dfs处理出这棵树的相关信息。然后就可以在logn的时间内完成一些操作。
倍增的应用中,最基础的应该就是求LCA(最近公共祖先),时间复杂度是logn。
对于求u、v的LCA,我们可以先把u、v用倍增法把深度大的提到和另一个深度相同。如果此时u、v已经相等了,表示原来u、v就在一条树链上,直接返回此时的结果。
如果此时u、v深度相同但不等,则证明他们的lca在更“浅”的地方,此时需要把u、v一起用倍增法上提到他们的父亲相等。为啥是提到父亲相等呢?因为倍增法是一次上提很多,所以有可能提“过”了,如果是判断他们本身作为循环终止条件,就无法判断是否提得过多了,所以要判断他们父亲是否相等。不懂的可以详见代码:
LCA(int u,int v) { if(dep[u]<dep[v])swap(u,v); //我们默认u的深度一开始大于v,那么如果u的深度小就交换u和v int delta=dep[u]-dep[v]; //计算深度差 for(int x=0;x<=max0;x++) //此循环用于提到深度相同。 if((1<<x)&delta) u=fa[u][x]; if(u==v)return u; for(int x=max0;x>=0;x--) //<strong>注意!此处循环必须是从大到小!</strong>因为我们应该越提越“精确”, if(fa[u][x]!=fa[v][x]) //如果从小到大的话就有可能无法提到正确位置,自己可以多想一下 { u=fa[u][x]; v=fa[v][x]; } return fa[u][0]; //此时u、v的第一个父亲就是LCA。 }
倍增还可以有很多变化,这让倍增法可以优更多的变化。比如用data[i][j]记录i到他的第2^j个父亲的路径长度,就可以边求LCA边求出两点距离,因为data[i][j]满足倍增的递推式:data[i][j]=data[i][j-1]+data[fa[i][j-1]][j-1]。或者用maxlen[i][j]记录i到第2^j个父亲的路径上最长边的边权,它满足maxlen[i][j]=max{maxlen[i][j-1],maxlen[fa[i][j-1]][j-1]},这样就可以快速求出两点路径上最长边的边权……
总之,倍增是一种较为基础的处理树的方式,一定要熟练掌握,可以打几个模板题先做做,然后找一些经典的倍增习题。
本文出处:http://blog.csdn.net/saramanda/article/details/54963914
- 树上倍增解析(转载)
- *树上倍增(LCA)
- 树上倍增
- NOIP 2013 货车运输(树上倍增)
- [BZOJ3732]Network(kruskal+树上倍增)
- 树链上的完全背包(树上倍增)
- CIA2 城市网络(树上倍增)
- NOIP模拟:情报传输(树上倍增)
- 【树上倍增算法模板】
- cf588e & bzoj3306 树上倍增
- noip2013truck树上路径倍增
- 3306: 树 树上倍增
- 树上倍增实现lca
- 树上倍增求LCA
- 【复习记录】树上倍增
- 树上倍增求LCA(最近公共祖先)
- NOIP2013 货车运输 (最大生成树+树上倍增LCA)
- [NOIP2013][CODEVS3287]货车运输(kruskal+树上倍增)
- jdbc简单的使用
- mysql导入数据库出现异常(数据库字符集问题),如何解决??
- 时间工具类
- uva 10003 lrj-P278 区间dp入门
- Invade the Mars HDU
- 树上倍增解析(转载)
- Windows——JDK安装与环境变量配置
- ConcurrentHashMap总结
- Selenium怎样搭配TestNG
- jediscluster工具类
- 重写memcpy函数
- RMRC2016 G:Flow Shop (DP)
- .NET中值类型比较的问题以及修复 (10 years old bug)
- 学习随笔——Java循环语句小例子