首页 > 技术文章 > 树链剖分

shzr 2018-06-23 08:58 原文

  树链剖分

 

  树链剖分听起来超级难,然而昨天听说我们这一届有同学已经会了,感觉压力很大。去看了一下所谓“树剖的前置知识”,惊奇的发现自己都会!于是莫名其妙的又学起了数据结构...noip前除了这个我就再不学超纲内容了...

  树链剖分:开始,有一个序列,要区间修改查询,于是有了线段树。后来,这个序列长成了一棵树,要路径修改查询、子树修改查询,于是有了树链剖分。

 

  找了好几个课件,然而还是看不懂,决定先在博客上理理思路再写。

  把一棵树切成多条链,就可以降低树上操作的复杂度了。但是怎么切比较好呢?按照一种的轻重链剖分的思想。以下是一些定义:

   重儿子:对于每一个非叶子节点,它的子节点中子树节点最多的节点为该节点的重儿子;

  轻儿子:对于每一个非叶子节点,它的儿子中非重儿子的儿子即为轻儿子;

  重边:连接两个重儿子的边叫做重边;

  轻边:不是重边的边;

  重链:相邻重边连起来的链叫重链;

  叶子节点,如果是轻儿子,则有一条以自己为起点的长度为1的链;

  重链以轻儿子为起点;

 

  第一次dfs:求取每一个节点的子树大小,深度,重儿子,父亲;

  第二次dfs:按照树的dfs序进行重新编号,给新编号赋初始值,处理每一个点所在链的顶端,处理每一条链。dfs的顺序要按照先重儿子后轻儿子的顺序,这样每条重链中的点在dfs序中是连续的。还有就是每个子树的编号也是连续的。

  如果正常的话现在大概已经开了7~8个数组了...

  

  于是就剖分完啦,现在来看看洛谷的树剖模板:

    1.$x$到$y$的路径上每个点点权加$z$;

    2.询问$x$,$y$路径上的点权和;

    3.以$x$为根的子树内点权全部加$z$;

    4.询问以$x$为根的子树的点权和。

  因为路径、子树现在都是连续的,于是就成了区间加,区间求和。用线段树。

  (插播一条新闻:在写下这句话之前,我刚刚过掉洛谷模板,所以之前说的话不保证对,下面说的话至少保证能过模板)

  其实到这里也就没什么东西了,路径点权求和和修改差不多,不停对不在同一条重链上的,链的初始位置更深的链进行求和,直到两点汇于一条链。

  子树就更简单啦,直接用子树节点个数加上根的新ID-1就是子树。


 

  [模板]树链剖分:https://www.luogu.org/problemnew/show/P3384

   
  1 # include <cstdio>
  2 # include <iostream>
  3 # define R register int
  4 # define nl n<<1
  5 # define nr n<<1|1
  6 
  7 using namespace std;
  8 
  9 const int maxn=100005;
 10 int rx,rf,h=0,n,m,r,p,x,y,z,coun=0;
 11 int A[maxn],firs[maxn<<1],nid[maxn];
 12 int F[maxn],dep[maxn],siz[maxn],son[maxn],Top[maxn];
 13 int a[maxn];
 14 int t[maxn<<2],delta[maxn<<2];
 15 char rc;
 16 
 17 inline int read()
 18 {
 19     rx=0,rf=1;
 20     rc=getchar();
 21     while (!isdigit(rc))
 22     {
 23         if(rc=='-') rf=-rf;
 24         rc=getchar();
 25     }
 26     while (isdigit(rc))
 27     {
 28         rx=(rx<<3)+(rx<<1)+(rc^48);
 29         rc=getchar();
 30     }
 31     return rx*rf;
 32 }
 33 
 34 struct edge
 35 {
 36     int nex,too;
 37 }g[maxn<<1];
 38 
 39 void add_edge(int x,int y)
 40 {
 41     g[++h].too=y;
 42     g[h].nex=firs[x];
 43     firs[x]=h;
 44 }
 45 
 46 void dfs1(int roo)
 47 {
 48     siz[roo]=1;
 49     int j,maxs=-1;
 50     for (R i=firs[roo];i;i=g[i].nex)
 51     {
 52         j=g[i].too;
 53         if(dep[j]) continue;
 54         F[j]=roo;
 55         dep[j]=dep[roo]+1;
 56         dfs1(j);
 57         siz[roo]+=siz[j];
 58         if(siz[j]>maxs)
 59         {
 60             maxs=siz[j];
 61             son[roo]=j;
 62         }
 63     }
 64     return ;
 65 }
 66 
 67 void dfs2(int x,int Tp)
 68 {
 69     nid[x]=++coun;
 70     a[coun]=A[x];
 71     Top[x]=Tp;
 72     if(!son[x]) return;
 73     dfs2(son[x],Tp);
 74     int j;
 75     for (R i=firs[x];i;i=g[i].nex)
 76     {
 77         j=g[i].too;
 78         if(j==F[x]||j==son[x]) continue;
 79         dfs2(j,j);
 80     }
 81     return ;    
 82 }
 83 
 84 inline void pushdown(int n,int l,int r)
 85 {
 86     int mid=(l+r)>>1,x=delta[n];
 87     delta[nl]+=x;
 88     delta[nr]+=x;
 89     delta[n]=0;
 90     t[nl]=(t[nl]+(mid-l+1)*x)%p;
 91     t[nr]=(t[nr]+(r-mid)*x)%p;
 92     return ;
 93 }
 94 
 95 inline void add(int n,int l,int r,int ll,int rr,int z)
 96 {
 97     if(ll<=l&&r<=rr)
 98     {
 99         delta[n]=(delta[n]+z)%p;
100         t[n]=(t[n]+(r-l+1)*z%p)%p;
101         return ;
102     }
103     int mid=(l+r)>>1;
104     if(delta[n]) pushdown(n,l,r);
105     if(ll<=mid) add(nl,l,mid,ll,rr,z);
106     if(rr>mid)  add(nr,mid+1,r,ll,rr,z);
107     t[n]=(t[nl]+t[nr])%p;
108     return ;
109 }
110 
111 inline int ask(int n,int l,int r,int ll,int rr)
112 {
113     if(ll<=l&&r<=rr)
114         return t[n];
115     int mid=(l+r)>>1,ans=0;
116     if(delta[n]) pushdown(n,l,r);
117     if(ll<=mid) ans=(ans+ask(nl,l,mid,ll,rr))%p;
118     if(rr>mid)   ans=(ans+ask(nr,mid+1,r,ll,rr))%p;
119     return ans;
120 }
121 
122 inline int ask1(int x,int y)
123 {
124     int ans=0;
125     while (Top[x]!=Top[y])
126     {
127         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
128         ans=(ans+ask(1,1,n,nid[Top[x]],nid[x]))%p;
129         x=F[ Top[x] ];
130     }
131     if(dep[x]>dep[y]) swap(x,y);
132     ans=(ans+ask(1,1,n,nid[x],nid[y]))%p;
133     return ans;
134 }
135 
136 inline void add1(int x,int y,int z)
137 {
138     z=z%p;
139     while (Top[x]!=Top[y])
140     {
141         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
142         add(1,1,n,nid[ Top[x] ],nid[x],z);
143         x=F[ Top[x] ];
144     }
145     if(dep[x]>dep[y]) swap(x,y);
146     add(1,1,n,nid[x],nid[y],z);
147 }
148 
149 inline int ask2(int x)
150 {
151     return ask(1,1,n,nid[x],nid[x]+siz[x]-1)%p;
152 }
153 
154 inline void add2(int x,int z)
155 {
156     add(1,1,n,nid[x],nid[x]+siz[x]-1,z);
157 }
158 
159 inline void build(int n,int l,int r)
160 {
161     if(l==r)
162     {
163         t[n]=a[l]%p;
164         return;
165     }
166     int mid=(l+r)>>1;
167     build(nl,l,mid);
168     build(nr,mid+1,r);
169     t[n]=(t[nl]+t[nr])%p;
170 }
171 
172 int main()
173 {
174     n=read(),m=read(),r=read(),p=read();
175     for (R i=1;i<=n;++i)
176         A[i]=read();
177     for (R i=1;i<n;++i)
178     {
179         x=read();
180         y=read();
181         add_edge(x,y);
182         add_edge(y,x);
183     }
184     dep[r]=1;
185     dfs1(r);
186     dfs2(r,r);
187     build(1,1,n);
188     int op;
189     for (R i=1;i<=m;++i)
190     {
191         op=read();
192         if(op==1)
193         {
194             x=read(),y=read(),z=read();
195             add1(x,y,z); 
196         }
197         if(op==2)
198         {
199             x=read(),y=read();
200             printf("%d\n",ask1(x,y));
201         }
202         if(op==3)
203         {
204             x=read(),z=read();
205             add2(x,z);
206         }
207         if(op==4)
208         {
209             x=read();
210             printf("%d\n",ask2(x));
211         }
212     }
213     return 0;
214 }
树链剖分

 

  树链剖分写起来很长,而且开了很多的数组,所以有一个必须注意的问题就是不要弄混了,下面是一些注意事项:

  1.如果一开始已经有点权,别忘了初始化线段树。因为线段树是为了后面的链剖准备的,里面的节点编号应该是新编的ID,所以要在两个$dfs$之后再初始化;

  2.如果出错了就一个函数一个函数的检查,因为比较复杂,不是很适合调试,所以仔细看,尤其是觉得不会错的地方一定不要放过。譬如我第一次做的时候就是把线段树的$pushdown$写错了;

  3.多看几个资料,别学成假的树剖了...比如重儿子的定义是子树大小最大,不是儿子数最大。发现好多资料都说的语焉不详,欺骗无知OIer。所以说一定要看代码!

  4.调试的时候构造数据,注意$ID[x]$不要正好等于$x$;


 

   2018.6.23:再过两个小时就要发中考成绩了!甚至感觉无心学习,不如敲个树剖板子虚度一下光阴,毕竟刚学完,$2h$还不一定够呢...接到新的通知,说18:00才发成绩啊...

  中考成绩发了,然而我没有做出来那道题...很奇妙,现在感觉中考成绩一点也不重要了,甚至不如树剖的一道题。

 

  [ZJOI2008]树的统计:https://www.luogu.org/problemnew/show/P2590

  题意概述:在一棵树上,支持路径求最大值,求和,单点修改。

  模板中的模板,但是得了5次20分,究其原因:每次交的都是同一个...只是在对拍的$my.cpp$里改了,交的却是$lg2590$...

  注意:查询时直接查询$x$,$y$;修改时是直接修改的线段树上的点,是按照新的$ID$建的树,所以要传进$id[x]$;

  
  1 # include <iostream>
  2 # include <cstdio>
  3 # include <cstring>
  4 # include <string>
  5 # define R register int
  6 # define nl n<<1
  7 # define nr n<<1|1
  8 
  9 using namespace std;
 10 
 11 const int maxn=30005;
 12 int h=0,n,q;
 13 int ra,rb,rx,rf;
 14 char rc;
 15 int coun=0,dep[maxn],Top[maxn],A[maxn],firs[maxn],siz[maxn],son[maxn],F[maxn],id[maxn],a[maxn],tm[maxn<<2];
 16 long long ts[maxn<<2];
 17 struct edge
 18 {
 19     int nex,too;
 20 }g[maxn<<1];
 21 
 22 inline int read()
 23 {
 24     rx=0,rf=1;
 25     rc=getchar();
 26     while (!isdigit(rc))
 27     {
 28         if(rc=='-') rf=-rf;
 29         rc=getchar();
 30     }
 31     while (isdigit(rc))
 32     {
 33         rx=(rx<<3)+(rx<<1)+(rc^48);
 34         rc=getchar();
 35     }
 36     return rx*rf;
 37 }
 38 
 39 inline void dfs1(int roo)
 40 {
 41     siz[roo]=1;
 42     int j,Maxx=-1;
 43     for (R i=firs[roo];i;i=g[i].nex)
 44     {
 45         j=g[i].too;
 46         if(dep[j]) continue;
 47         dep[j]=dep[roo]+1;
 48         F[j]=roo;
 49         dfs1(j);
 50         siz[roo]+=siz[j];
 51         if(siz[j]>Maxx)
 52         {
 53             Maxx=siz[j];
 54             son[roo]=j;
 55         }
 56     }
 57     return ;
 58 }
 59 
 60 inline void dfs2(int x,int Tp)
 61 {
 62     int j;
 63     Top[x]=Tp;
 64     id[x]=++coun;
 65     a[coun]=A[x];
 66     if(!son[x]) return ;
 67     dfs2(son[x],Tp);
 68     for (R i=firs[x];i;i=g[i].nex)
 69     {
 70         j=g[i].too;
 71         if(j==F[x]||j==son[x]) continue;
 72         dfs2(j,j);
 73     }
 74     return ;
 75 }
 76 
 77 inline void build(int n,int l,int r)
 78 {
 79     if(l==r)
 80     {
 81         tm[n]=ts[n]=a[l];
 82         return ;
 83     }
 84     int mid=(l+r)>>1;
 85     build(nl,l,mid);
 86     build(nr,mid+1,r);
 87     ts[n]=ts[nl]+ts[nr];
 88     tm[n]=max(tm[nl],tm[nr]);
 89 }
 90 
 91 inline void add(int a,int b)
 92 {
 93     g[++h].too=b;
 94     g[h].nex=firs[a];
 95     firs[a]=h;
 96 }
 97 
 98 inline void change(int n,int l,int r,int pos,int v)
 99 {
100     if(l==r)
101     {
102         tm[n]=ts[n]=v;    
103         return ;    
104     }
105     int mid=(l+r)>>1;
106     if(pos<=mid) change(nl,l,mid,pos,v);
107     if(pos>mid)  change(nr,mid+1,r,pos,v);
108     tm[n]=max(tm[nl],tm[nr]);
109     ts[n]=ts[nl]+ts[nr];
110     return ;
111 }
112 
113 inline long long asks(int n,int l,int r,int ll,int rr)
114 {
115     if(ll<=l&&r<=rr)
116         return ts[n];
117     int mid=(l+r)>>1;
118     long long ans=0;
119     if(ll<=mid) ans+=asks(nl,l,mid,ll,rr);
120     if(rr>mid)  ans+=asks(nr,mid+1,r,ll,rr);
121     return ans;
122 }
123 
124 inline int askm(int n,int l,int r,int ll,int rr)
125 {
126     if(ll<=l&&r<=rr)
127         return tm[n];
128     int mid=(l+r)>>1;
129     int ans=-999999;
130     if(ll<=mid) ans=max(ans,askm(nl,l,mid,ll,rr));
131     if(rr>mid)  ans=max(ans,askm(nr,mid+1,r,ll,rr));
132     return ans;
133 }
134 
135 inline long long ask_sum(int x,int y)
136 {
137     long long ans=0;
138     while (Top[x]!=Top[y])
139     {
140         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
141         ans+=asks(1,1,n,id[ Top[x] ],id[x]);
142         x=F[ Top[x] ];
143     }
144     if(dep[x]>dep[y]) swap(x,y);
145     ans+=asks(1,1,n,id[x],id[y]);
146     return ans;
147 }
148 
149 inline int ask_max(int x,int y)
150 {
151     int ans=-30009;
152     while (Top[x]!=Top[y])
153     {
154         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
155         ans=max(ans,askm(1,1,n,id[ Top[x] ],id[x]));
156         x=F[ Top[x] ];
157     }
158     if(dep[x]>dep[y]) swap(x,y);
159     ans=max(ans,askm(1,1,n,id[x],id[y]));
160     return ans;
161 }
162 
163 int main()
164 {
165     n=read();
166     for (int i=1;i<n;i++)
167     {
168         ra=read();
169         rb=read();
170         add(ra,rb);
171         add(rb,ra);
172     }
173     for (int i=1;i<=n;i++)
174         A[i]=read();
175     dep[1]=1;
176     dfs1(1);
177     dfs2(1,1);
178     build(1,1,n);
179     q=read();
180     string op;
181     int x,y;
182     while (q--)
183     {
184         cin>>op;
185         x=read();
186         y=read();
187         if(op=="CHANGE") change(1,1,n,id[x],y);
188         if(op=="QMAX") printf("%d\n",ask_max(x,y));
189         if(op=="QSUM") printf("%lld\n",ask_sum(x,y));
190     }
191     return 0;
192 }
[ZJOI2008]树的统计

 


 

   [SHOI2012]魔法树:https://www.luogu.org/problemnew/show/P3833

  题意概述:区间加,子树求和。

  竟然又是一个模板题,原来去某夏令营的时候听说每个模板至少敲五遍才能记住,那今天写完这个题,明天后天再各写一个类模板题,再往后找一点配合树链剖分的非模板题做一做。

   
# include <cstdio>
# include <iostream>
# define R register int
# define nl n<<1
# define nr n<<1|1

using namespace std;

const int maxn=100009;
struct edge
{
    int nex,too;
}g[maxn<<1];
char rc,op;
int h,coun,d,rx,rf,u,v,n,q,F[maxn],son[maxn],id[maxn],dep[maxn],siz[maxn],Top[maxn],firs[maxn];
long long c[maxn<<2],delta[maxn<<2];

inline int read()
{
    rx=0,rf=1;
    rc=getchar();
    while (!isdigit(rc))
    {
        if(rc=='-') rf=-rf;
        rc=getchar();
    }
    while (isdigit(rc))
    {
        rx=(rx<<3)+(rx<<1)+(rc^48);
        rc=getchar();
    }
    return rx*rf;
}

inline void dfs1(int roo)
{
    siz[roo]=1;
    int j,maxs=-1;
    for (R i=firs[roo];i;i=g[i].nex)
    {
        j=g[i].too;
        if(dep[j]) continue;
        dep[j]=dep[roo]+1;
        F[j]=roo;
        dfs1(j);
        siz[roo]+=siz[j];
        if(siz[j]>maxs)
        {
            maxs=siz[j];
            son[roo]=j;
        }
    }
    return ;
}

inline void dfs2(int x,int Tp)
{
    int j;
    Top[x]=Tp;
    id[x]=++coun;
    if(!son[x]) return ;
    dfs2(son[x],Tp);
    for (R i=firs[x];i;i=g[i].nex)
    {
        j=g[i].too;
        if(j==F[x]||j==son[x]) continue;
        dfs2(j,j);
    }
    return ;
}

inline void pushdown(int n,int l,int r)
{
    long long x=delta[n];
    int mid=(l+r)>>1;
    delta[n]=0;
    delta[nl]+=x;
    delta[nr]+=x;
    c[nl]+=(mid-l+1)*x;
    c[nr]+=(r-mid)*x;
    return ;
}

inline void add(int n,int l,int r,int ll,int rr,int v)
{
    if(ll<=l&&r<=rr)
    {
        delta[n]+=v;
        c[n]+=(r-l+1)*v;
        return ;
    }
    int mid=(l+r)>>1;
    if(delta[n]) pushdown(n,l,r);
    if(ll<=mid) add(nl,l,mid,ll,rr,v);
    if(rr> mid) add(nr,mid+1,r,ll,rr,v);
    c[n]=c[nl]+c[nr];
    return ;
}

inline long long ask(int n,int l,int r,int ll,int rr)
{
    if(ll<=l&&r<=rr)
    {
        return c[n];
    }
    int mid=(l+r)>>1;
    long long ans=0;
    if(delta[n]) pushdown(n,l,r);
    if(ll<=mid) ans+=ask(nl,l,mid,ll,rr);
    if(rr>mid)  ans+=ask(nr,mid+1,r,ll,rr);
    return ans;
}

inline void add_l(int x,int y,int d)
{
    while (Top[x]!=Top[y])
    {
        if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
        add(1,1,n,id[ Top[x] ],id[x],d);
        x=F[ Top[x] ];
    }
    if(dep[x]>dep[y]) swap(x,y);
    add(1,1,n,id[x],id[y],d);
    return ;
}

inline long long ask_s(int x)
{
    return ask(1,1,n,id[x],id[x]+siz[x]-1);
}

inline void add_edge(int x,int y)
{
    g[++h].too=y;
    g[h].nex=firs[x];
    firs[x]=h;
}

int main()
{
    scanf("%d",&n);
    for (R i=1;i<n;++i)
    {
        u=read(),v=read();
        add_edge(u,v),add_edge(v,u);
    }
    dep[0]=1;
    dfs1(0);
    dfs2(0,0);
    scanf("%d",&q);
    while (q--)
    {
        cin>>op;
        if(op=='A')
        {
            u=read(),v=read(),d=read();
            add_l(u,v,d);
        }
        else
        {
            u=read();
            printf("%lld\n",ask_s(u));
        }
    }
    return 0;
}
[SHOI2012]魔法树

 


 

  染色:https://www.lydsy.com/JudgeOnline/problem.php?id=2243

  题意概述:给定一棵无根树,支持路径染色,路径查询有多少"段"颜色.$n<=10^5$

  什么是一"段"颜色,就是极长的一段相同颜色.

  如果是序列上的问题,可以通过线段树记录区间答案,左右端点颜色来快速的做,但是到树上...树剖就好了呀。这里要注意分别保存两个起点目前最高处的颜色,如果有时要交换两个端点,那么这个也要换,等到最后一次查询时两个端点都要检查一下是否和旁边的合成了一段。然后...别忘了pushdown...估计除了我也没人会忘了...

  
  1 # include <cstdio>
  2 # include <iostream>
  3 # include <cstring>
  4 # include <string>
  5 # include <algorithm>
  6 # include <cmath>
  7 # define R register int
  8 # define nl (n<<1)
  9 # define nr (n<<1|1)
 10 
 11 using namespace std;
 12 
 13 const int maxn=100005;
 14 int n,m,c[maxn],x,y,h,firs[maxn],dep[maxn],id[maxn],f[maxn],son[maxn],siz[maxn],top[maxn],cnt,a[maxn],col;
 15 char st[5];
 16 struct node
 17 {
 18     int l,r,ans,delta;
 19 }t[maxn<<2];
 20 struct edge
 21 {
 22     int too,nex;
 23 }g[maxn<<1];
 24 
 25 int read()
 26 {
 27     R x=0;
 28     char c=getchar();
 29     while(!isdigit(c)) c=getchar();
 30     while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
 31     return x;
 32 }
 33 
 34 void add (int x,int y)
 35 {
 36     g[++h].nex=firs[x];
 37     firs[x]=h;
 38     g[h].too=y;
 39 }
 40 
 41 void dfs1 (int x)
 42 {
 43     int j,maxx=0;
 44     siz[x]=1;
 45     for (R i=firs[x];i;i=g[i].nex)
 46     {
 47         j=g[i].too;
 48         if(dep[j]) continue;
 49         dep[j]=dep[x]+1;
 50         f[j]=x;
 51         dfs1(j);
 52         siz[x]+=siz[j];
 53         if(siz[j]>maxx) maxx=siz[j],son[x]=j;
 54     }
 55 }
 56 
 57 void dfs2 (int x,int Tp)
 58 {
 59     int j;
 60     id[x]=++cnt;
 61     a[cnt]=c[x];
 62     top[x]=Tp;
 63     if(!son[x]) return;
 64     dfs2(son[x],Tp);
 65     for (R i=firs[x];i;i=g[i].nex)
 66     {
 67         j=g[i].too;
 68         if(j==son[x]||j==f[x]) continue;
 69         dfs2(j,j);
 70     }
 71 }
 72 
 73 node update (node a,node b)
 74 {
 75     node x;
 76     x.ans=a.ans+b.ans;
 77     if(a.r==b.l) x.ans--;
 78     x.l=a.l;
 79     x.r=b.r;
 80     x.delta=-1;
 81     return x;
 82 }
 83 
 84 void pushdown (int n)
 85 {
 86     int x=t[n].delta;
 87     t[nl].l=t[nr].l=t[nl].r=t[nr].r=x;
 88     t[nl].delta=t[nr].delta=x;
 89     t[n].delta=-1;
 90     t[nl].ans=t[nr].ans=1;    
 91 }
 92 
 93 void build (int n,int l,int r)
 94 {
 95     if(l==r)
 96     {
 97         t[n].ans=1;
 98         t[n].l=t[n].r=a[l];
 99         t[n].delta=-1;
100         return;
101     }
102     int mid=(l+r)>>1;
103     build(nl,l,mid);
104     build(nr,mid+1,r);
105     t[n]=update(t[nl],t[nr]);
106     t[n].delta=-1;
107 }
108 
109 void chan (int n,int l,int r,int ll,int rr,int col)
110 {
111     if(ll<=l&&r<=rr)
112     {
113         t[n].ans=1;
114         t[n].l=t[n].r=col;
115         t[n].delta=col;
116         return;
117     }
118     int mid=(l+r)>>1;
119     if(t[n].delta!=-1) pushdown(n);
120     if(ll<=mid) chan(nl,l,mid,ll,rr,col);
121     if(rr>mid) chan(nr,mid+1,r,ll,rr,col);
122     t[n]=update(t[nl],t[nr]);
123 }
124 
125 node ask (int n,int l,int r,int ll,int rr)
126 {
127     if(ll<=l&&r<=rr) return t[n];
128     int mid=(l+r)>>1;
129     if(t[n].delta!=-1) pushdown(n);
130     if(rr<=mid) return ask(nl,l,mid,ll,rr);
131     if(ll>mid) return ask(nr,mid+1,r,ll,rr);
132     return update(ask(nl,l,mid,ll,rr),ask(nr,mid+1,r,ll,rr));
133 }
134 
135 int ask_link (int x,int y)
136 {
137     node t;
138     int colx=-1,coly=-1,ans=0;
139     while(top[x]!=top[y])
140     {
141         if(dep[ top[x] ]<dep[ top[y] ]) swap(x,y),swap(colx,coly);
142         t=ask(1,1,n,id[ top[x] ],id[x]);
143         if(t.r==colx) ans+=t.ans-1;
144         else ans+=t.ans;
145         colx=t.l;
146         x=f[ top[x] ];
147     }
148     if(dep[x]>dep[y]) swap(x,y),swap(colx,coly);
149     t=ask(1,1,n,id[x],id[y]);
150     ans+=t.ans;
151     if(t.l==colx) ans--;
152     if(t.r==coly) ans--;
153     return ans;         
154 }
155 
156 void cov (int x,int y,int col)
157 {
158     while(top[x]!=top[y])
159     {
160         if(dep[ top[x] ]<dep[ top[y] ]) swap(x,y);
161         chan(1,1,n,id[ top[x] ],id[x],col);
162         x=f[ top[x] ];
163     }
164     if(dep[x]>dep[y]) swap(x,y);
165     chan(1,1,n,id[x],id[y],col);
166 }
167 
168 int main()
169 {
170     n=read(),m=read();
171     for (R i=1;i<=n;++i) c[i]=read();
172     for (R i=1;i<n;++i)
173     {
174         x=read(),y=read();
175         add(x,y);
176         add(y,x);
177     }
178     dep[1]=1;
179     dfs1(1);
180     dfs2(1,1);
181     build(1,1,n);
182     for (R i=1;i<=m;++i)
183     {
184         scanf("%s",st);
185         if(st[0]=='C')
186         {
187             x=read(),y=read(),col=read();
188             cov(x,y,col);
189         }
190         else
191         {
192             x=read(),y=read();
193             printf("%d\n",ask_link(x,y));
194         }
195     }
196     return 0;
197 }
染色

  


  

  以下就是手残选手的一些心得,手不残的同学就不用往下看了...没再有题了。

  从零开始写链剖:

   首先先读入边,点权。如果给定了根,一定要用它给的根,如果没有,看看编号从几开始,选择最小编号作为根。先给根的深度赋值为$1$,然后进行两次dfs。先用语言叙述一下,为了防止手残的进一步发展,再贴上这块代码。在第一遍$dfs$中,先将当前的点的子树和赋为1(自己),定义一个变量保存目前这个节点的儿子中子树和最多的,可以先赋值为-1,遍历邻接表。如果遍历到深度为$0$的点,就认为是这个根的儿子并进行赋值($dep[j]=dep[roo]+1$),$roo$自然就成为了$F[j]$,别忘了赋值;接下来以$j$为根接着遍历下去,完成后判断是否为最大的子树,如果是,就$son[roo]=j,maxs=siz[j]$;

   
 1 void dfs1(int roo)
 2 {
 3     siz[roo]=1;
 4     int j,maxs=-1;
 5     for (R i=firs[roo];i;i=g[i].nex)
 6     {
 7         j=g[i].too;
 8         if(dep[j]) continue;
 9         F[j]=roo;
10         dep[j]=dep[roo]+1;
11         dfs1(j);
12         siz[roo]+=siz[j];
13         if(siz[j]>maxs)
14         {
15             maxs=siz[j];
16             son[roo]=j;
17         }
18     }
19     return ;
20 }
dfs_1

 

    $dfs_2$中,传入的是$(roo,roo)$,进入时,给当前的$roo$赋值$ID$,如果有读入的点权,就赋值给新的$ID$的权值数组。如果是叶子,就可以$return$了,然后赋值$Top$,对于重儿子接着递归$dfs_2(son[x],Tp)$;循环给其他儿子加链,循环时排除父亲和重儿子,$dfs_2(j,j)$。

   
 1 void dfs2(int x,int Tp)
 2 {
 3     nid[x]=++coun;
 4     a[coun]=A[x];
 5     Top[x]=Tp;
 6     if(!son[x]) return;
 7     dfs2(son[x],Tp);
 8     int j;
 9     for (R i=firs[x];i;i=g[i].nex)
10     {
11         j=g[i].too;
12         if(j==F[x]||j==son[x]) continue;
13         dfs2(j,j);
14     }
15     return ;    
16 }
dfs_2

  

   这样两个$dfs$就写完了,对于新的权值数组建树,线段树相关操作能别再错了吗...每当区间分开成两半进行查询之前别忘了清空$delta$标记,建树以及区间加完了别忘记$update$。

   现在就只剩修改查询了,还是比较轻松愉快的。

  修改和查询几乎一模一样,首先从子树修改开始看:

  1 inline void add2(int x,int z)
  2 {
  3     add(1,1,n,nid[x],nid[x]+siz[x]-1,z);
  4 }

   非常简单吧,注意这里传进来的x应该是原数组的下表,而不是新的$ID$。

  加上一个子树查询:

  1 inline int ask2(int x)
  2 {
  3     return ask(1,1,n,nid[x],nid[x]+siz[x]-1)%p;
  4 }

    然后是稍微复杂的路径修改查询:

 1 inline int ask1(int x,int y)
 2 {
 3     int ans=0;
 4     while (Top[x]!=Top[y])
 5     {
 6         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
 7         ans=(ans+ask(1,1,n,nid[Top[x]],nid[x]))%p;
 8         x=F[ Top[x] ];
 9     }
10     if(dep[x]>dep[y]) swap(x,y);
11     ans=(ans+ask(1,1,n,nid[x],nid[y]))%p;
12     return ans;
13 }
 1 inline void add1(int x,int y,int z)
 2 {
 3     z=z%p;
 4     while (Top[x]!=Top[y])
 5     {
 6         if(dep[ Top[x] ]<dep[ Top[y] ]) swap(x,y);
 7         add(1,1,n,nid[ Top[x] ],nid[x],z);
 8         x=F[ Top[x] ];
 9     }
10     if(dep[x]>dep[y]) swap(x,y);
11     add(1,1,n,nid[x],nid[y],z);
12 }

   要分清楚$x$与$ID[x]$。只要$x$,$y$还不在同一条链上,就把$x$调到链头深度较深的地方;

  对$ID[x]$到$ID[ Top[x] ]$($x$所在链的链头)进行区间加。把$x$跳到链头的父亲处,进入新的一条链中(两条重链之间必然由一条轻链连在一起)

  当$x$,$y$到了同一条链上,就把x调到深度较浅的地方,对于$ID[x]$,$ID[y]$,这一段进行区间加。

  为了不要出错,这个板子可以多打打。

  完。

  

---shzr

推荐阅读