| 树链剖分初步

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

 

概念:

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

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

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

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

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

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

 

拆分的步骤:

  • 标明重儿子;

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

(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

 

posted @ 2019-04-29 16:08  Oranges  阅读(245)  评论(0编辑  收藏  举报