树上倍增 x

树上倍增.

dfs序的做法:

思路:

//f[i][j]表示dfs序中序号为i到序号为j的点之间深度最小的点的编号
dfs序[]//存0-...(id)编号
节点[]//存dfs序中所经过的节点号
dep[]//存走过的点所对应的点的深度
i : 1 2 3 4 5 6 7 ......
id[i] : (该点在节点编号中第一次出现的位置所对应的dfs序编号)
//ans表示求u节点到i节点的最短距离...
ans=f[id[u]][id[v]];

至于代码……额……没写

树上倍增还能够用来求LCA

其中f[i,j]表示i的第2^j祖先dfs预处理f[i,j]=f[f[i,j-1],j-1];

对于每一对x,y先将深度调成一样再枚举j逐一往上找,这两个过程都是log的

代码如下:

#include <iostream>
#include <cstdio>
#include <cmath>
//maybe my English is not very good 

using namespace std;

const int M = 5e5 + 1;
int n,m,s;
int num;
int deep[M],h[M];
bool vs[M];
int jumps[M][21];
int p;

struct A{
    int next;
    int to;
}t[M<<1];

inline int read() //optimize
{
    int x=0,f=1;char ch=getchar();

    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) f=-1;
        ch=getchar();
    }

    while(ch>=‘0‘&&ch<=‘9‘)
    {
        x=x*10+ch-‘0‘;
        ch=getchar();
    }

    return x*f;
}

void ADD(int x,int y) //connect the x and the y
{
    num++;
    t[num].to=y;
    t[num].next=h[x];
    h[x]=num;
}

void Dfs(int u)
{
    for(int i=h[u];i!=-1;i=t[i].next)
    {
        int v=t[i].to; //u‘s next side
        if(deep[v] == 0) //if v is not visited
        {
            deep[v]=deep[u]+1; //deep+1
            jumps[v][0]=u; //u is v‘s dad
            Dfs(v); //continue Dfs
        }
    }
}

void steps()
{
    p=int(log(n)/log(2)+0.001); //find the biggest
    for(int i=1;i<=p;i++) //the Limit
        for(int j=1;j<=n;j++)
            jumps[j][i]=jumps[jumps[j][i-1]][i-1];
//the j jump 2^i can get to the (first jump 2^(i-1),then jump 2^i-1 can get to)
//eh...I will speak in Chinese.
//because 倍增 is use 次方的形式 increase
}

int LCA(int a,int b)
{
    //We let the b‘s deep is small
    if(deep[a]<deep[b]) swap(a,b);
    for(int i=p;i>=0;i--)
    {//first let the a jump to the b‘s deep
        if(deep[jumps[a][i]]>=deep[b])
        a=jumps[a][i];
    }
    if(a == b) return b; //if the b is them‘s LCA , return b
    for(int i=p;i>=0;i--) //jump together
    {
        if(jumps[a][i]!=jumps[b][i])
        a=jumps[a][i],b=jumps[b][i]; //update
    }
    return jumps[a][0];
}

int main()
{
    //s is the root
    n=read();m=read();s=read();
    for(int i=1;i<=n;i++) h[i]=-1;
    int x,y;
    for(int i=1;i<n;i++)
    {
        x=read();y=read();
        //connect the x and the y
        ADD(x,y);
        ADD(y,x);
    }
    deep[s]=1; //this is too important !!!
    //if you don‘t think so ,"//" it.
    //and then you will know
    Dfs(s); //Dfs the root(s)
    steps(); //find the steps
    int a,b;
    while(m--)
    {
        a=read();b=read();
        printf("%d\n",LCA(a,b));
    }
    return 0;
}

树上倍增英文版???

1

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<stdio.h>
#include<vector>
#define maxn 500500
using namespace std;
///隶属邻接表
struct Edge{                    //邻接表的结构体
    int from,to;
}edges[2*maxn];                 //边要乘2,因为是无向图 ;
int first[maxn],next[2*maxn];   //同理;
int read(){                        //读入优化,可以照着这个模板来写,这个还算写的比较好看。
    int re=0;
    char ch=getchar();
    while (ch<‘0‘ || ch>‘9‘) ch=getchar();
    while (ch>=‘0‘ && ch<=‘9‘){
        re=re*10+ch-‘0‘;
        ch=getchar();
    }
    return re;
}
///////////////////////////////////////////////
///全局变量
int n,m;
int root;
int height[maxn];
float log2n;
///////////////////////////////////////////////////////
///隶属LCA的全局变量
int f[maxn][20];//
int have[maxn];                           //have,有没有找过,这都是套路 。
void dfs(int u,int h){                 //u代表点的标号,h代表高度。
    int v;
    height[u]=h;
    for(int i=1;i<=log2n;i++) {
        if(h<=(1<<i)) break;              //由于i是从小到大计算的,故(1<<i)>=h 时可直接退出。请务必想清楚是<=  还是=。
        f[u][i] = f[ f[u][i-1] ][i-1]; //动规计算。同样也是一切倍增算法的核心。
    }
    int k=first[u];
    while(k!=-1){
        v=edges[k].to;
        if(!have[v]) {
            have[v]=1;
            f[v][0]=u;                 //将要找的下一个点的父节点标为当前处理的节点u。
            dfs(v,h+1);
        }
        k=next[k];
    }
}
int require_LCA(int a,int b){
    int da=height[a],db=height[b];
//第一步,将a,b两点移到同样的高度,只动高度大的那个点而不动高度小的那个点。
    if(da!=db) {
        if(da<db){                   //保证a的高度是大于b的高度的。
            swap(a,b);
            swap(da,db);
        }
        int d=da-db;
        for(int i=0;i<=log2n;i++)
            if( (1<<i) & d) a=f[a][i]; //这里的位运算可以减少代码量
                                       //考虑到d是一个定值,而(1<<i)在二进制中只有第(i+1)位是1;
                                       //那么d与(1<<i)如果某一位为1,那么表示可以向上移动,
                                       //如果此时不移动,那么i增大了后就无法使height[a]==height[b]了
    }
//第二步,找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了
//从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
//如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
//是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i];
    if(a==b) return b;
    int i=0;
    for(i=log2n;i>=0;i--) {
        if(height[ f[a][i] ]<0) continue;
        if( f[a][i]==f[b][i] ) continue;
        else a=f[a][i],b=f[b][i];        //顺便一提,在第二步任何地方没有break;
                                       //我就是因为在这里写了一个break,然后找了我两个小时啊。
    }
    return f[a][0];
}
/////////////////////////////////
///据说从主函数开始阅读是个好习惯。
int main(){
//    freopen("in2.txt","r",stdin);
    n=read();m=read();root=read();
    memset(first,-1,sizeof(first));
    memset(next,-1,sizeof(next));
    int s,t;
    int dsd=2*(n-1);
    for(int i=1;i<=dsd;i+=2) {
        s=read();t=read();      //读入优化。
        edges[i].from=s;
        edges[i].to=t;
        edges[i+1].from=t;
        edges[i+1].to=s;
        next[i]=first[s];
        first[s]=i;
        next[i+1]=first[t];
        first[t]=i+1;
    }
    // 以上是邻接表,在此不再赘述。
    log2n=log(n)/log(2)+1;        //C++计算log是自然对数,我们要用的以2为底的对数,故要除以log(2);
                                  //对无理数加上1或是0.5是个好习惯,可以减小误差;
    memset(have,0,sizeof(have));
    memset(height,0,sizeof(height));
    memset(f,-1,sizeof(f));
    have[root]=1;                //fa[][]和height[]要在dfs理进行计算,不然根本找不到某个非根节点的父亲是谁;
    dfs(root,1);
    for(int i=1;i<=n;i++){
        for(int j=0;j<=log2n;j++) {
            if(height[i] <=(1<<j) ) break;
        }
    }
    for(int i=0;i<m;i++) {      //应对要求进行求解。
        s=read();t=read();
        int y=require_LCA(s,t);
        printf("%d\n",y);
    }
    return 0;
}

2

End.

时间: 2024-08-24 13:05:36

树上倍增 x的相关文章

【BZOJ 3551】[ONTAK2010] Peaks加强版 Kruskal重构树+树上倍增+主席树

这题真刺激...... I.关于Kruskal重构树,我只能开门了,不过补充一下那玩意还是一棵满二叉树.(看一下内容之前请先进门坐一坐) II.原来只是用树上倍增求Lca,但其实树上倍增是一种方法,Lca只是他的一种应用,他可以搞各种树上问题,树上倍增一般都会用到f数组. |||.我们跑出来dfs序就能在他的上面进行主席树了. IV.别忘了离散. V.他可能不连通,我一开始想到了,但是我觉得出题人可能会是好(S)人(B),但是...... #include <cstdio> #include

【树上倍增】【分类讨论】水果姐逛水果街Ⅱ

3305 水果姐逛水果街Ⅱ 时间限制: 2 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 水果姐第二天心情也很不错,又来逛水果街. 突然,cgh又出现了.cgh施展了魔法,水果街变成了树结构(店与店之间只有一条唯一的路径). 同样还是n家水果店,编号为1~n,每家店能买水果也能卖水果,并且同一家店卖与买的价格一样. cgh给出m个问题,每个问题要求水果姐从第x家店出发到第y家店,途中只能选一家店买一个水果,然后选一家店(可以是同一家店,

「Nescaf&#233;26」 Freda的传呼机 【树上倍增+图论】

题目: 为了随时与rainbow快速交流,Freda制造了两部传呼机.Freda和rainbow所在的地方有N座房屋.M条双向光缆.每条光缆连接两座房屋,传呼机发出的信号只能沿着光缆传递,并且传呼机的信号从光缆的其中一端传递到另一端需要花费t单位时间.现在Freda要进行Q次试验,每次选取两座房屋,并想知道传呼机的信号在这两座房屋之间传递至少需要多长时间.Freda和rainbow简直弱爆了有木有T_T,请你帮帮他们吧--N座房屋通过光缆一定是连通的,并且这M条光缆有以下三类连接情况:A:光缆不

图论-最近公共祖先-在线树上倍增

有关概念: 最近公共祖先(LCA,Lowest Common Ancestors):对于有根树T的两个结点u.v,最近公共祖先表示u和v的深度最大的共同祖先. 树上倍增是求LCA的在线算法(对于每一个询问输入后即计算) 思路: fa[i][j]表示编号为j的结点从下往上的第2i个祖先 即fa[0][j]表示j的父结点,递推式为fa[i][j]=fa[i-1][fa[i-1][j]],即j的第2i个祖先为j的第2i-1个祖先的第2i-1个祖先 另:不存在第2i个祖先(即2i超过j的深度)时,f[i

【bzoj4281】[ONTAK2015]Zwi?zek Harcerstwa Bajtockiego 树上倍增+LCA

题目描述 给定一棵有n个点的无根树,相邻的点之间的距离为1,一开始你位于m点.之后你将依次收到k个指令,每个指令包含两个整数d和t,你需要沿着最短路在t步之内(包含t步)走到d点,如果不能走到,则停在最后到达的那个点.请在每个指令之后输出你所在的位置. 输入 第一行包含三个正整数n,m,k(1<=m<=n<=1000000,1<=k<=1000000). 接下来n-1行,每行包含两个正整数x,y(1<=x,y<=n),描述一条树边. 接下来k行,每行两个整数d,t

HDU 4822 Tri-war(LCA树上倍增)(2013 Asia Regional Changchun)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4822 Problem Description Three countries, Red, Yellow, and Blue are in war. The map of battlefield is a tree, which means that there are N nodes and (N – 1) edges that connect all the nodes. Each country

【bzoj3779】重组病毒 LCT+树上倍增+DFS序+树状数组区间修改区间查询

题目描述 给出一棵n个节点的树,每一个节点开始有一个互不相同的颜色,初始根节点为1. 定义一次感染为:将指定的一个节点到根的链上的所有节点染成一种新的颜色,代价为这条链上不同颜色的数目. 现有m次操作,每次为一下三种之一: RELEASE x:对x执行一次感染: RECENTER x:把根节点改为x,并对原来的根节点执行一次感染: REQUEST x:询问x子树中所有节点感染代价的平均值. 输入 输入的第一行包含两个整数n和m,分别代表局域网中计算机的数量,以及操作和询问的总数.接下来n-1行,

BZOJ 4082 Wf2014 Surveillance 树上倍增

题目大意:给定一个n个点的环,有k个区间,要求选择最少的区间覆盖所有点 首先我们考虑链上版本,显然我们有一个贪心的做法: 从1号节点开始,每次选择能向后走的最远的区间,直到走完所有节点为止 正确性显然 但是到了环上版本我们却不能直接套用这个算法,因为环上不存在所谓的"1号节点" 因此我们这样做: 拆环后将序列倍增,把所有区间按照右端点从小到大排序 每个区间向这个区间右端点向后能走的最远的区间连一条边 这样我们会得到一棵树 从每个点可以用树上倍增求出最少向上走多少个点可以绕环走一圈 数据

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数. $|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ . 题解 广义后缀自动机+树上倍增+线段树合并 对 $S$