这两天的学习有些难受,连续值班爆肝加上学长讲的dp每次都听得不是很懂,题也没做多少,感觉自己对这两天自学的一些“添头儿”倒是领会得挺快(树剖,矩阵加速,概率问题,几何问题等),这些写成博客也没多少内容,所以今天只是填个坑,希望以后的学习内容友好一点,好歹能让我写出来点东西吧!
总的来说,树链剖分是把树结构分成不同的链,将树上路径、子树等问题转化为链上问题进行解决,比如dfs序就是一种将子树转化成链的方法,这里也有类似dfs序的内容。
我们对着板子题讲
先看变量声明部分
int n,m,root,mod;//根据题目要求,n个节点,m次操作,root为根,mod模数
int a[maxn],a0[maxn],id[maxn];//a存储初始权值,id存树上上节点dfs后的新编号,a0也是存初始权值,不过下标是节点新编号,这个数组和id一起求出
int h[maxn],nxt[maxn],v[maxn],p;//大家喜闻乐见的链式前向星
int hson[maxn],top[maxn],sz[maxn],cntn,depth[maxn],fa[maxn];//hson存储每个节点的重儿子,top是每个节点所在链的顶端节点,sz是以此节点为根的子树大小,cntn用来更新新的编号,depth存节点深度,fa记录每个节点的父亲
struct lineartree//线性树(棒读
{
int l,r;
int sum,lazy;
}t[maxn<<2];//大家喜闻乐见的线段树,用来处理树破开成链后的各种操作问题,当然segmenttree似乎更正确一些,不过懒得改了
线段树我们先不管他,首先要进行的是两次dfs,
第一次,求出每个节点的父亲,深度,子树大小和重儿子
void dfsforhson(int x,int f,int d)//当前节点x,它的父亲f,深度d
{
sz[x]=1;//一开始每个节点的子树大小先赋值为1
depth[x]=d;
fa[x]=f;
int sonmax=-1;//x的最大子树大小
for(int i=h[x];i;i=nxt[i])
{
int to=v[i];
if(to==f)continue;//防止它搜回去出现蜜汁问题
dfsforhson(to,x,d+1);
sz[x]+=sz[to];//加上儿子的子树大小
if(sz[to]>=sonmax)//更新重儿子,即子树最大的儿子
{
sonmax=sz[to];
hson[x]=to;
}//这里自己曾经写错,一定要记住这是对x进行处理,要更新的不是它父亲f的重儿子
}
}
第二个dfs,记录遍历到每个节点的顺序,存到id数组,并将数组a的值对应新编号转移到a0中。这里要注意先处理重儿子所在的链,至于原因,ldy学长曾在讲启发式合并的时候说过减少重链的遍历次数可以使时间复杂度更优,我觉得对这个dfs来说应该也是这样
void dfsforindex(int x,int xtop)//x和它的链顶节点
{
id[x]=++cntn;
a[cntn]=a0[x];//记录新编号,给a0赋值
top[x]=xtop;//别忘了记录
if(!hson[x])return;//没有儿子了直接返回
dfsforindex(hson[x],xtop);//先处理重儿子,注意重链都是由重儿子彼此相连组成的,也就是每条重链上的节点,其链顶都是相同的(有点啰嗦)
for(int i=h[x];i;i=nxt[i])
{
int to=v[i];
if(to==fa[x]||to==hson[x])continue;//防止搜回去或者再走一遍重儿子
dfsforindex(to,to);//对于每个轻儿子,都有一个以它为顶端的轻链
}
}
这里结合图好理解一些,其实图片来自这篇博客,非原创,但是我不知道怎么去水印,侵删。在这个图上可以看出,每条链的起点都是轻儿子。
接下来,我们发现这棵树已经用一维id数组存了下来,就可以用线段树处理了。
分别考虑题目要求的操作:
1 将x到y的路上所有节点点权都加z。
处理方法是先让链顶所在深度较深的节点,跳到其链顶的父节点,反复进行,直到两点位于同一链上,由于每条链上节点的id都是连续的,所以可以用线段树区间操作解决。
2 x到y路径点权求和,这个操作和1完全类似
void roadupdate(int x,int y,int change)
{
change%=mod;
while(top[x]!=top[y])
{
if(depth[top[x]] > depth[top[y]])swap(x,y);//这里可能不太好理解,可以看看图上标记了4和5的节点,发现如果按照节点的深度判断而不是按照其链顶的深度,会有几个点重复计算,答案就不对了
update(1,id[top[y]],id[y],change);
y=fa[top[y]];
}
if(depth[x] > depth[y])swap(x,y);
update(1,id[x],id[y],change);
}
int roadquery(int x,int y)
{
int ans=0;
while(top[x]!=top[y])
{
if(depth[top[x]] > depth[top[y]])swap(x,y);//
ans=(ans+query(1,id[top[y]],id[y]))%mod;
y=fa[top[y]];
}
if(depth[x] > depth[y])swap(x,y);
ans=(ans+query(1,id[x],id[y]))%mod;
return ans;
}
至于3 4,都是对子树的操作,可以发现x的子树节点范围是id[x]到id[x]+sz[x]-1,这个和dfs序很像,我们可以发现不仅是在重链上,在任一子树上id[]也是连续的,这也许有助于理解第二次dfs以及其中先处理重链的操作。
if(q==3)
{
int x=getnum(),z=getnum();
update(1,id[x],id[x]+sz[x]-1,z);
}
if(q==4)
{
int x=getnum();
printf("%lld\n",query(1,id[x],id[x]+sz[x]-1));
}
以下为这个模板题的完整ac代码
#include <iostream>
#include <cstdio>
#define ll long long
const int maxn=200000+5;
using namespace std;
int n,m,root,mod,p;
int a[maxn],a0[maxn],id[maxn];
int h[maxn],nxt[maxn],v[maxn];
int hson[maxn],top[maxn],sz[maxn],cntn,depth[maxn],fa[maxn];
struct lineartree
{
int l,r;
int sum,lazy;
}t[maxn<<2];
void dfsforhson(int x,int f,int d)
{
sz[x]=1;
depth[x]=d;
fa[x]=f;
int sonmax=-1;//
for(int i=h[x];i;i=nxt[i])
{
int to=v[i];
if(to==f)continue;
dfsforhson(to,x,d+1);
sz[x]+=sz[to];
if(sz[to]>=sonmax)
{
sonmax=sz[to];
hson[x]=to;
}
}
}
void dfsforindex(int x,int xtop)
{
id[x]=++cntn;
a[cntn]=a0[x];
top[x]=xtop;//
if(!hson[x])return;
dfsforindex(hson[x],xtop);
for(int i=h[x];i;i=nxt[i])
{
int to=v[i];
if(to==fa[x]||to==hson[x])continue;
dfsforindex(to,to);
}
}
int getnum()
{
int num=0;
bool flag=1;
char c=getchar();
while(!isdigit(c))
{
if(c=='-')flag=0;
c=getchar();
}
while(isdigit(c))
{
num=num*10+c-'0';
c=getchar();
}
return flag ? num : -num;
}
void add(int x,int y)
{
++p;
v[p]=y;
nxt[p]=h[x];
h[x]=p;
}
void pushup(int d)
{
t[d].sum=(t[d<<1].sum+t[d<<1|1].sum)%mod;
return ;
}
void pushdown(int d,int lazy)
{
t[d].lazy=(t[d].lazy+lazy)%mod;
t[d].sum=(t[d].sum+lazy*(t[d].r-t[d].l+1))%mod;
return ;
}
void build(int d,int l,int r)
{
t[d].l=l;
t[d].r=r;
t[d].lazy=0;
if(l==r)
{
t[d].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(d<<1,l,mid);
build(d<<1|1,mid+1,r);
pushup(d);
}
void update(int d,int l,int r,int change)
{
if(t[d].l>=l&&t[d].r<=r)
{
t[d].sum+=(t[d].r-t[d].l+1)*change;
t[d].lazy+=change;
t[d].sum%=mod;
t[d].lazy%=mod;
return;
}
int mid=(t[d].l+t[d].r)>>1;
if(t[d].lazy!=0)
{
pushdown(d<<1,t[d].lazy);
pushdown(d<<1|1,t[d].lazy);
t[d].lazy=0;
}
if(mid>=l)update(d<<1,l,r,change);
if(mid<r)update(d<<1|1,l,r,change);
pushup(d);
}
int query(int d,int l,int r)
{
if(t[d].l>=l&&t[d].r<=r)
{
return t[d].sum;
}
int mid=(t[d].l+t[d].r)>>1;
if(t[d].lazy!=0)
{
pushdown(d<<1,t[d].lazy);
pushdown(d<<1|1,t[d].lazy);
t[d].lazy=0;
}
int ans=0;
if(mid>=l)ans=(ans+query(d<<1,l,r))%mod;
if(mid<r)ans=(ans+query(d<<1|1,l,r))%mod;
return ans;
}
int roadquery(int x,int y)
{
int ans=0;
while(top[x]!=top[y])
{
if(depth[top[x]] > depth[top[y]])swap(x,y);//
ans=(ans+query(1,id[top[y]],id[y]))%mod;
y=fa[top[y]];
}
if(depth[x] > depth[y])swap(x,y);
ans=(ans+query(1,id[x],id[y]))%mod;
return ans;
}
void roadupdate(int x,int y,int change)
{
change%=mod;
while(top[x]!=top[y])
{
if(depth[top[x]] > depth[top[y]])swap(x,y);
update(1,id[top[y]],id[y],change);
y=fa[top[y]];
}
if(depth[x] > depth[y])swap(x,y);
update(1,id[x],id[y],change);
}
int main()
{
n=getnum(),m=getnum(),root=getnum(),mod=getnum();
for(int i=1;i<=n;i++)
{
a0[i]=getnum();
}
for(int i=1;i<n;i++)
{
int x=getnum(),y=getnum();
add(x,y);
add(y,x);
}
dfsforhson(root,0,1);
dfsforindex(root,root);
build(1,1,n);
for(int i=1;i<=m;i++)
{
int q=getnum();
if(q==1)
{
int x=getnum(),y=getnum(),z=getnum();
roadupdate(x,y,z);
}
if(q==2)
{
int x=getnum(),y=getnum();
printf("%lld\n",roadquery(x,y));
}
if(q==3)
{
int x=getnum(),z=getnum();
update(1,id[x],id[x]+sz[x]-1,z);
}
if(q==4)
{
int x=getnum();
printf("%lld\n",query(1,id[x],id[x]+sz[x]-1));
}
}
return 0;
}
终于写完了嘿嘿嘿
update:8.8
补充一个线段树知识点,如果题目涉及区间修改和单点修改,记得在单点修改函数加pushdown,不然会wa穿的。
蛤OI2015树上操作
void updatex(int d,int x,ll change)//节点x的值增加change
{
if(t[d].l==x&&t[d].r==x)
{
t[d].sum+=change;
return ;
}
int mid=(t[d].l+t[d].r)>>1;
pushdown(d);
if(x<=mid)
{
updatex(d<<1,x,change);
}
else
{
updatex(d<<1|1,x,change);
}
pushup(d);
}