| 树链剖分初步

树链剖分是将一棵树拆分为多条链进行多次操作的算法,本质是对树上问题的优化。

 

概念:

重儿子:父亲节点的所有儿子中子树结点数目最多的结点;

轻儿子:父亲节点中除了重儿子以外的儿子;

重边:父亲结点和重儿子连成的边;

轻边:父亲节点和轻儿子连成的边;

重链:由多条重边连接而成的路径;

轻链:由多条轻边连接而成的路径;

 

拆分的步骤:

  • 标明重儿子;

遍历树,处理每个点的深度、父节点、重儿子。

(dfs

  • 连接重链并对所有点重新标号;

再次遍历树,并且按遍历顺序对点进行编号,编号时优先遍历重儿子及其子树,以保证重链上编号的连续性(即连接重链)。

(dfs

 

在两次dfs后可以保证每一条重链上点的编号连续。这时对每一条链维护,本质是LCA(因此第一次遍历需要记录深度),记录每一个点所在的重链的顶点top,可以更快地处理。与LCA相同,每次只选择顶点top的深度更深的点向上跳跃。

 

 

/*
点权树链剖分 
*/
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m;
struct node{
    int v,next,w;
    node(){}
    node(int a,int b,int c) : v(a), next(b), w(c){} 
}edge[N*2];
int head[N];
int cnt;

int father[N];
int son[N];//重结点,叶子结点没有重结点 
int deep[N];
int size[N];//子树大小 

int dfs[N];//dfs序号,也即新编号 
int order;
int top[N];//所在重链的顶点 

int work(int l,int r)
{
    //后续处理; 
}
/* 第一次遍历:father、deep、son */ 
void dfs1(int u,int fa,int depth)
{
    father[u]=fa;
    deep[u]=depth;
    size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        if(edge[i].v==fa) continue;
        dfs1(edge[i].v,fa,depth+1);
        size[u]+=size[edge[i].v];
        if(size[edge[i].v]>size[son[u]])
        {
            son[u]=edge[i].v;
        }
    }
}
 
/* 第二次遍历:top、dfs */ 
void dfs2(int u,int TOP)
{
    top[u]=TOP;
    dfs[u]=++order;
    if(!son[u]) return;
    /* 优先处理son 保证重链上的编号连续 */ 
    dfs2(son[u],TOP);  
    for(int i=head[u];i;i=edge[i].next)
    {
        /* 处理轻链的第一个点 */ 
        if(edge[i].v!=father[u]&&edge[i].v!=son[u])
        {
            /* 轻链的重儿子为它本身 */ 
            dfs2(edge[i].v,edge[i].v);
        }
    }
}
int query(int x,int y)
{
    int sum=0;
    /* LCA :每次优先选择终点深度更深的点跳跃 且每次只操作一个点避免擦肩 */ 
    while(deep[top[x]]!=deep[top[y]])
    {
        if(deep[top[x]]>deep[top[y]]) 
        {
            sum+= work(dfs[x],dfs[top[x]]);
            
        }
        else sum+= work(dfs[y],dfs[top[y]]);
    }
    /* 最后两点重合或在同一重链上 */ 
    if(x==y) return sum;
    if(deep[x]>deep[y]) sum+= work(dfs[x],dfs[son[y]]-1);
    else sum+= work(dfs[y],dfs[son[x]]-1);
    return sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edge[++cnt]=node(b,head[a],c);head[a]=cnt;
        edge[++cnt]=node(a,head[b],c);head[b]=cnt;
    }
    dfs1(1,1,1);
    dfs2(1,1);
    scanf("%d%d",&U,&V);
    printf("%d",query(U,V)); 
}
 
View Code

 

 

根据具体的题目,边权树链剖分和点权树链剖分有一点区别。

学习时看的blog: 树链剖分模板 边权、点权区别

 

例题

1.2019 ICPC南昌邀请赛-J题

(边权树链剖分+树状数组

   听说也有LCA、线段树和主席树的写法(._. )不过我不会,所以写了树状数组

   树状数组比较短 (._. )

DSM(Data Structure Master) once learned about tree when he was preparing for NOIP(National Olympiad in Informatics in Provinces) in Senior High School. So when in Data Structure Class in College, he is always absent-minded about what the teacher says.

The experienced and knowledgeable teacher had known about him even before the first class. However, she didn't wish an informatics genius would destroy himself with idleness. After she knew that he was so interested in ACM(ACM International Collegiate Programming Contest), she finally made a plan to teach him to work hard in class, for knowledge is infinite.

This day, the teacher teaches about trees." A tree with nnn nodes, can be defined as a graph with only one connected component and no cycle. So it has exactly n?1n-1n?1 edges..." DSM is nearly asleep until he is questioned by teacher. " I have known you are called Data Structure Master in Graph Theory, so here is a problem. "" A tree with nnn nodes, which is numbered from 111 to nnn. Edge between each two adjacent vertexes uuu and vvv has a value w, you're asked to answer the number of edge whose value is no more than kkk during the path between uuu and vvv."" If you can't solve the problem during the break, we will call you DaShaMao(Foolish Idiot) later on."

The problem seems quite easy for DSM. However, it can hardly be solved in a break. It's such a disgrace if DSM can't solve the problem. So during the break, he telephones you just for help. Can you save him for his dignity?
Input

In the first line there are two integers n,mn,mn,m, represent the number of vertexes on the tree and queries(2≤n≤105,1≤m≤1052 \le n \le 10^5,1 \le m \le 10^52≤n≤105,1≤m≤105)

The next n?1n-1n?1 lines, each line contains three integers u,v,wu,v,wu,v,w, indicates there is an undirected edge between nodes uuu and vvv with value www. (1≤u,v≤n,1≤w≤1091 \le u,v \le n,1 \le w \le 10^91≤u,v≤n,1≤w≤109)

The next mmm lines, each line contains three integers u,v,ku,v,ku,v,k , be consistent with the problem given by the teacher above. (1≤u,v≤n,0≤k≤109)(1 \le u,v \le n,0 \le k \le 10^9)(1≤u,v≤n,0≤k≤109)
Output

For each query, just print a single line contains the number of edges which meet the condition.
样例输入1

3 3
1 3 2
2 3 7
1 3 0
1 2 4
1 2 7

样例输出1

0
1
2

样例输入2

5 2
1 2 1000000000
1 3 1000000000
2 4 1000000000
3 5 1000000000
2 3 1000000000
4 5 1000000000

样例输出2

2
4

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
const int Inf=1000000005;
int n,m;
struct node{
    int v,next,w;
    node(){}
    node(int a,int b,int c) : v(a), next(b), w(c){} 
}edge[N*2];
int head[N];
int cnt;

int father[N];
int son[N];
int deep[N];
int size[N];

int dfs[N];
int order;
int top[N];

int Tree[N];

struct NODE{
    int u,v,w,id,if_query;
    NODE(){}
    NODE(int a,int b,int c,int d,int e) : u(a),v(b),w(c),id(d),if_query(e){}
}Q[N*2];

int ans[N];
bool cmp(NODE Q1,NODE Q2)
{
    if(Q1.w==Q2.w) return Q1.if_query<Q2.if_query;
    return Q1.w<Q2.w;
}
void dfs1(int u,int fa,int depth)
{
    father[u]=fa;
    deep[u]=depth;
    size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        if(edge[i].v==fa) continue;
        Q[edge[i].v].u=u;
        Q[edge[i].v].v=edge[i].v;
        Q[edge[i].v].w=edge[i].w;
        Q[edge[i].v].id=edge[i].v;
        dfs1(edge[i].v,u,depth+1);
        size[u]+=size[edge[i].v];
        if(size[edge[i].v]>size[son[u]])
        {
            son[u]=edge[i].v;
        }
    }
}
void dfs2(int u,int TOP)
{
    top[u]=TOP;
    dfs[u]=++order;
    if(!son[u]) return;
    dfs2(son[u],TOP); 
    for(int i=head[u];i;i=edge[i].next)
    {
        if(edge[i].v!=father[u]&&edge[i].v!=son[u])
        {
            dfs2(edge[i].v,edge[i].v);
        }
    }
}
void build(int x)
{
    while(x<=n)
    {
        Tree[x]++;
        x+=x&(-x);
    }
}
int query_tree(int x)
{
    int sum=0;
    while(x)
    {
        sum+=Tree[x];
        x-=x&(-x);
    }
    return sum;
}
int query(int x,int y)
{
    int sum=0;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]>deep[top[y]])
        {
            sum+=query_tree(dfs[x])-query_tree(dfs[top[x]]-1);
            x=father[top[x]];
        }
        else{
            sum+=query_tree(dfs[y])-query_tree(dfs[top[y]]-1);
            y=father[top[y]];
        }
    }
    if(x==y) return sum;
    if(deep[x]>deep[y]) sum+=query_tree(dfs[x])-query_tree(dfs[son[y]]-1);
    else sum+=query_tree(dfs[y])-query_tree(dfs[son[x]]-1);
    return sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edge[++cnt]=node(b,head[a],c);head[a]=cnt;
        edge[++cnt]=node(a,head[b],c);head[b]=cnt;
        Q[i].if_query=0;
    }
    Q[1].w=Inf;
    Q[1].u=Q[1].v=1;
    Q[1].id=1; 
    dfs1(1,0,1);
    dfs2(1,1);
    
    for(int i=1;i<=m;i++)
    {
        int a,b,k;
        scanf("%d%d%d",&a,&b,&k);
        Q[i+n]=NODE(a,b,k,i+n,1);
    }
    
    sort(Q+1,Q+n+m+1,cmp);
    
    for(int i=1;i<=n+m;i++)
    {
        if(Q[i].if_query)
        {
            ans[Q[i].id-n]=query(Q[i].u,Q[i].v);
        }
        else {
            if(deep[top[Q[i].u]]>deep[top[Q[i].v]])
            {
                build(dfs[Q[i].u]);
            }
            else build(dfs[Q[i].v]);
        }
    }
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/orange-/p/10790969.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
04-26
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值