DFS序就是将树的节点按照先根的顺序遍历得到的节点顺序

性质:一个子树全在一个连续的区间内,可以与线段树和树状数组搭配使用

很好写,只需在dfs中加几行代码即可。

代码:

void dfs(ll u,ll fat)
{
st[u]=++tim;//st记录出现位置,tim记录当前的时间戳
dfn[tim]=u;//dfn记录dfs序
for(rll i=h[u];i;i=e[i].nxt)//遍历
{
ll v=e[i].v;
if(v!=fat)
{
dfs(v,u);//搜索
}
}
en[u]=tim;//记录结束位置
}

你以为这就完了,不你想的真美,这才刚刚开始。


DFS序通常与线段树、树状数组配合使用,这个使用,其实就是在dfs序上建立树状数组和线段树

DFS序有7种典型的模型


一、

点修改,子树和查询

题目传送门:#144. DFS 序 1 - 题目 - LibreOJ (loj.ac)

大体意思:有一棵树,有点权,进行m次操作,有关于点权的修改,有查询子树和的操作,现在,按照要求完成代码。

修改点权,查子树和,子树在DFS序上又是连续的,这不就是单点修改,区间查询吗?因为是求和,我们搭配树状数组来完成题目。

先建立DFS序,在DFS序上建立树状数组,后面操作和树状数组基本一样,只是要注意,查询的区间的范围是这个节点在DFS序上的开始点-1~结束点,具体看代码,有注释

代码:

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
const ll N=1e6+5;
ll n,p,cnt,tim,m;
ll h[N],st[N],en[N],a[N],dfn[N],s[N];
struct edge
{
ll v,nxt;
} e[N<<1];
inline ll read()
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(ll u,ll v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(ll u,ll fat)
{
st[u]=++tim;//建立DFS序,记录开始点
dfn[tim]=u;//这个其实有没有都一样,后边不会用到,可以用来查错误
//如果MLE了,优先考虑把dfn数组删除
for(rll i=h[u];i;i=e[i].nxt)
{
ll v=e[i].v;
if(v!=fat)
{
dfs(v,u);
}
}
en[u]=tim;//记录结束点
}
void pchange(ll x,ll y)//单点修改
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;
}
}
ll sum(ll x)//区间查询
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=s[i];
}
return ans;
}
int main()
{
n=read(),m=read(),p=read();//n 节点数 m 操作数 p 根节点
for(rll i=1;i<=n;++i)
{
a[i]=read();//读入每个节点的信息
}
for(rll i=1;i<n;++i)
{
ll x=read(),y=read();
add(x,y);//建边
add(y,x);
}
dfs(p,0);//搜索
for(rll i=1;i<=n;++i)
{
pchange(st[i],a[i]);//先将各个点的原始信息存入树状数组
}
for(rll i=1;i<=m;++i)
{
ll op=read();
if(op==1)
{
ll x=read(),y=read();
pchange(st[x],y);
}
else
{
ll x=read();
printf("%lld\n",sum(en[x])-sum(st[x]-1));
//这里st[x]-1~en[x]是子树的范围,自己可以模拟理解一下
}
}
return 0;
}

二、三、(题目在一块儿,所以放一块讲)

树链修改,点查询,子树和查询

题目传送门:#146. DFS 序 3,树上差分 1 - 题目 - LibreOJ (loj.ac)

有一棵树,有点权,进行m次操作,有关于树链的修改,有查询点的操作,有查询子树和的操作,现在,按要求完成代码。

树链修改,就是把点a到点b的链上所有的节点都加上v。

在这里,我们要搞清楚一个地方,就是这个修改是给谁做贡献!

这里要用树上差分,以后做补充,这里简单讲一下。

(a->b)的修改对(a->b)上的点的贡献是v,我们再把他拆分一下,(a->lca)和(b->lca),这样改的话,那我们就将a,b点的修改的含义变为(a->root)和(b->root)上的点有加v,so,(lca->root)上的点就加了两次,我们要抵消这次加法,所以在点lca上-2*v,但lca属于树链a->b上的点,也要+v,所以lca上相当于只-v,我们只能把lca的父节点-v,以到平衡。

代码实现就是add(st[a],v);add(st[b],v),add(st[lca],-v),add(st[fa[lca]],-v)

我们将每个点的修改都放到树状数组中,最后求和,再加上原数据即可,具体看代码,有注释。

求子树和的链修改也是一样的道理,但是,修改点x,对一棵树y的贡献值是不一样多的,它的贡献是(deep[y]-deep[x]+1)*v(v这里是修改值),我们将式子拆开,就变成了(deep[y])*v-(deep[x]-1)*v,deep[y]、deep[x]是可知的,v在输入后也是可知的,但对于一整棵子树,公式就变成了∑(deep[y])*v-(deep[x]-1)*v,(deep[y])*v我们可以通过树状数组来记录求和,(deep[x]-1)是已知的,我们只要在记录v的和就好了,当然,我们现在统计的只是变化的数值,到时还要在加上原数据,原数据单独存在一个数组中,不会敲可以看代码,有注释。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
#define rint register int
using namespace std;
const ll N=1e6+5;
ll n,cnt,m,p,tim;
int a[N],h[N],st[N],en[N];//a 每个点的信息 h 遍历需要 st 记录开始位置 en记录结束位置
int stf[N][20],lg[N],deep[N];//stf st表求lca lg 处理log deep 记录深度
ll ss[N],s1[N],s2[N];//ss 存储原始子树和 s1 存储每个数的变化 s2 存储乘积
struct edge
{
int v,nxt;
} e[N<<1];
inline ll read()//快读
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u,int fat)
{
st[u]=++tim;//记录开始位置
deep[u]=deep[fat]+1;//记录深度
stf[u][0]=fat;//记录父节点,更新st表
ss[u]=a[u];//记录子树和,先把当前节点加上
for(rint i=1;i<=lg[deep[u]];++i)//更新st表
{
stf[u][i]=stf[stf[u][i-1]][i-1];
}
for(rint i=h[u];i;i=e[i].nxt)//遍历
{
int v=e[i].v;
if(v!=fat)//只要不是父节点
{
dfs(v,u);//就搜索
ss[u]+=ss[v];//累加子树和
}
}
en[u]=tim;//记录结束位置
}
int LCA(int x,int y)//基本操作,求lca
{
if(deep[x]<deep[y])
{
x=x^y,y=x^y,x=x^y;
}
while(deep[x]>deep[y])
{
x=stf[x][lg[deep[x]-deep[y]]-1];
}
if(x==y) return x;
for(rint i=lg[deep[x]]-1;i>=0;--i)
{
if(stf[x][i]!=stf[y][i])
{
x=stf[x][i];
y=stf[y][i];
}
}
return stf[x][0];
}
void ad(int x,ll y,ll z)//x:位置 y:变化(+ or -) z:乘积
{
for(rint i=x;i<=n;i+=(i&(-i)))//树状数组修改点
{
s1[i]+=y;
s2[i]+=z;
}
}
void in(int x,ll d)//ad函数的引子,处理特殊情况
{
if(x==0) return;//父节点不能为0
ad(st[x],d,d*deep[x]);//d:每个点的修改 d*deep[x]:乘积
}
ll sum(int x,ll t[])//树状数组求和
{
ll ans=0;
for(rint i=x;i;i-=(i&(-i)))
{
ans+=t[i];
}
return ans;
}
ll query(int x,ll t[])//sum函数的引子,处理sum(en[x],t)-sum(st[x]-1,t)
{
return sum(en[x],t)-sum(st[x]-1,t);
}
int main()
{
n=read(),m=read(),p=read();
for(rll i=1;i<=n;++i)
{
a[i]=read();//读入信息
}
for(rll i=1;i<n;++i)
{
ll u=read(),v=read();
add(u,v);//建边
add(v,u);
}
for(rll i=1;i<=n;++i)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);//处理log
}
dfs(p,0);//搜索,建dfs序
for(rll i=1;i<=m;++i)
{
ll op=read();
if(op==1)
{
ll a=read(),b=read(),c=read();
ll lca=LCA(a,b);//求lca
in(a,c);in(b,c);in(lca,-c);in(stf[lca][0],-c);//树上差分
}
if(op==2)
{
ll p=read();
printf("%lld\n",query(p,s1)+a[p]);//查询点
}
if(op==3)//(deep[y]-deep[x]+1)*c=deep[y]*c-(deep[x]-1)*c 求子树和公式
{
ll p=read();
printf("%lld\n",query(p,s2)-query(p,s1)*(deep[p]-1)+ss[p]);//查询子树和
//query(p,s2):deep[y]*c
//query(p,s1):c的总和
//so query(p,s1)*(deep[p]-1):(deep[x]-1)*c
//ss[p] 原来的子树和
}
}
return 0;
}

树链修改,点查询单独代码:

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
const ll N=1e5+5;
ll n,cnt,m,p,tim;
ll a[N],h[N],dfn[N],st[N],en[N];
ll stf[N][20],lg[N],deep[N];
ll s[N];
struct edge//链式前向星
{
ll v,nxt;
} e[N<<1];
inline ll read()//快读
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(ll u,ll v)//建边
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(ll u,ll fat)
{
st[u]=++tim;//记录开始位置
dfn[tim]=u;//记录时间戳
deep[u]=deep[fat]+1;//更新深度
stf[u][0]=fat;//更新st表
for(rll i=1;i<=lg[deep[u]];++i)//更新st表
{
stf[u][i]=stf[stf[u][i-1]][i-1];
}
for(rll i=h[u];i;i=e[i].nxt)
{
ll v=e[i].v;
if(v!=fat)//不能搜到父节点
{
dfs(v,u);
}
}
en[u]=tim;//记录结束位置
}
ll LCA(ll x,ll y)//基本操作求lca
{
if(deep[x]<deep[y])
{
x=x^y,y=x^y,x=x^y;
}
while(deep[x]>deep[y])
{
x=stf[x][lg[deep[x]-deep[y]]-1];
}
if(x==y) return x;
for(rll i=lg[deep[x]]-1;i>=0;--i)
{
if(stf[x][i]!=stf[y][i])
{
x=stf[x][i];
y=stf[y][i];
}
}
return stf[x][0];
}
void ad(ll x,ll y)//修改
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;
}
}
ll sum(ll x)//求和(这里求的是修改的值,也就是将要对原数值修改的值)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=s[i];
}
return ans;
}
int main()
{
n=read(),p=read();
for(rll i=1;i<=n;++i)
{
a[i]=read();
}
for(rll i=1;i<n;++i)
{
ll u=read(),v=read();
add(u,v);
add(v,u);
}
for(rll i=1;i<=n;++i)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
dfs(p,0);
m=read();
for(rll i=1;i<=m;++i)
{
ll k=read();
if(k==1)
{
ll a=read(),b=read(),c=read();
ll lca=LCA(a,b);
ad(st[a],c),ad(st[b],c);
ad(st[lca],-c),ad(st[stf[lca][0]],-c);//树上差分
}
else
{
ll p=read();
printf("%lld\n",a[p]+sum(en[p])-sum(st[p]-1));//记得加上原数值
}
}
}

树链修改,子树和查询单独代码:

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
const ll N=1e5+5;
ll n,cnt,m,p,tim;
ll a[N],h[N],dfn[N],st[N],en[N];
ll stf[N][20],lg[N],deep[N];
ll ss[N],s1[N],s2[N];//s1 记录加减 s2 记录乘积
struct edge
{
ll v,nxt;
} e[N<<1];
inline ll read()
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(ll u,ll v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(ll u,ll fat)
{
st[u]=++tim;//记录出现位置
dfn[tim]=u;//记录dfs序
deep[u]=deep[fat]+1;//记录深度
stf[u][0]=fat;//记录父节点
ss[u]=a[u];//记录子树和,后面会被加
for(rll i=1;i<=lg[deep[u]];++i)
{
stf[u][i]=stf[stf[u][i-1]][i-1];//更新st表
}
for(rll i=h[u];i;i=e[i].nxt)//遍历
{
ll v=e[i].v;
if(v!=fat)
{
dfs(v,u);//搜索
ss[u]+=ss[v];//更新子树和
}
}
en[u]=tim;//记录结束位置
}
ll LCA(ll x,ll y)//基本操作求lca
{
if(deep[x]<deep[y])
{
x=x^y,y=x^y,x=x^y;
}
while(deep[x]>deep[y])
{
x=stf[x][lg[deep[x]-deep[y]]-1];
}
if(x==y) return x;
for(rll i=lg[deep[x]]-1;i>=0;--i)
{
if(stf[x][i]!=stf[y][i])
{
x=stf[x][i];
y=stf[y][i];
}
}
return stf[x][0];
}
void ad(ll x,ll y,ll z)
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s1[i]+=y;//记录加减
s2[i]+=z;//记录乘积
}
}
void in(ll x,ll d)
{
if(x==0) return;//防止父节点是0
ad(st[x],d,d*deep[x]);
}
ll sum(ll x,ll t[])//求和
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=t[i];
}
return ans;
}
ll query(ll x,ll t[])
{
return sum(en[x],t)-sum(st[x]-1,t);
}
int main()
{
n=read(),m=read(),p=read();
for(rll i=1;i<=n;++i)
{
a[i]=read();//记录各个点的信息
}
for(rll i=1;i<n;++i)
{
ll u=read(),v=read();
add(u,v);//建边
add(v,u);//建边
}
for(rll i=1;i<=n;++i)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);//求log
}
dfs(p,0);//搜索
for(rll i=1;i<=m;++i)
{
ll op=read();//(deep[y]-deep[x]+1)*c=deep[y]*c-(deep[x]-1)*c
if(op==1)
{
ll a=read(),b=read(),c=read();
ll lca=LCA(a,b);
in(a,c);in(b,c);in(lca,-c);in(stf[lca][0],-c);//差分
}
else
{
ll p=read();
printf("%lld\n",query(p,s2)-query(p,s1)*(deep[p]-1)+ss[p]);
//query(p,s2):deep[y]*c
//query(p,s1):c的总和
//so query(p,s1)*(deep[p]-1):(deep[x]-1)*c
//ss[p] 原来的子树和
}
}
}

展开代码


四、点修改,链和查询

题意:有一棵树,有点权。进行n次点权修改,n次提问以某2个节点间的树链的权值和。

点y+v,y的子树到root的距离都会+v,所以点修改就转化成了子树修改,子树在dfs序上是连续的区间,利用差分思想,在起始点+v,结束点+1的位置-v,求a-b的链和,其实就是(a->root)+(b->root)-(lca->root)-(st[lca][0]->root)的值,因为点lca也是在链上的,所以只减去一遍(lca->root),再减去(st[lca][0]->root)来抵消,代码有注释。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e6+6;
int n,m,R,cnt,tim;
int h[N],lg[N],deep[N],st[N][20],be[N],en[N];
ll d[N],s[N],size[N];
struct edge
{
int v,nxt;
} e[N<<1];
inline ll read()//快读
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(int u,int v)//建边
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u,int fat)
{
deep[u]=deep[fat]+1;//记录深度
st[u][0]=fat;//更新st表
be[u]=++tim;//记录开始位置
size[u]+=d[u];//记录点到root的距离
for(rint i=1;i<=lg[deep[u]];++i)//更新st表
{
st[u][i]=st[st[u][i-1]][i-1];
}
for(rint i=h[u];i;i=e[i].nxt)//传统技能——遍历
{
int v=e[i].v;
if(v!=fat)//不能是父节点,否则等RE吧
{
size[v]+=size[u];//子节点到root的距离是父节点的加上这个边的距离
dfs(v,u);//搜索
}
}
en[u]=tim;//记录结束位置
}
void ad(int x,int c)//单点修改
{
if(x==0) return;
for(rint i=x;i<=n;i+=(i&(-i)))
{
s[i]+=c;
}
}
int LCA(int x,int y)//基本操作——求lca
{
if(deep[x]<deep[y])
{
x=x^y,y=x^y,x=x^y;
}
while(deep[x]>deep[y]) x=st[x][lg[deep[x]-deep[y]]-1];
if(x==y) return x;
for(rint i=lg[deep[x]]-1;i>=0;--i)
{
if(st[x][i]!=st[y][i])
{
x=st[x][i];
y=st[y][i];
}
}
return st[x][0];
}
ll sum(int x)//树状数组求和
{
ll ans=0;
for(rint i=x;i;i-=(i&(-i)))
{
ans+=s[i];
}
return ans;
}
int main()
{
n=read(),m=read(),R=read();//n 点数 m 操作数 R 根节点
for(rint i=1;i<=n;++i)
{
d[i]=read();//读入每个点的数据
}
for(rint i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y);//建边
add(y,x);
}
for(rint i=1;i<=n;++i)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);//处理log
}
dfs(R,0);//深搜
for(rint i=1;i<=m;++i)
{
int op=read();
if(op==1)
{
int x=read(),y=read();
ad(be[x],y);ad(en[x]+1,-y);//树上差分
}
if(op==2)
{
int a=read(),b=read(),lca=LCA(a,b);
ll ans=0;
ans+=size[a]+size[b]-size[lca]-size[st[lca][0]];
ans+=sum(be[a])+sum(be[b])-sum(be[lca])-sum(be[st[lca][0]]);//(a->root)+(b->root)-(lca->root)-(st[lca][0]->root)
printf("%lld\n",ans);
}
}
}

还请自己多画图理解一下,想清楚每个数组的含义!


五、子树修改,点查询

题意:有一棵树,有点权。进行n次子树权修改,也就是子树上每一个点的权都加v,n次提问以某点的权值。

这个比较水,一个子树的修改,不会影响其他子树,子树在dfs序上又是连续的区间,直接用差分就好了,点查询就是树状数组求和再加原数据就好了,直接上代码,看到这,你应该对大体基本步骤很清楚了,往下的代码对基本步骤不再进行讲解了。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e6+5;
int n,m,R,cnt,tim;
int h[N],be[N],en[N];//看到这应该很熟了,基本操作不讲了
ll d[N],s[N];
struct edge
{
int v,nxt;
} e[N<<1];
inline ll read()
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u,int fat)
{
be[u]=++tim;
for(rint i=h[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v!=fat)
{
dfs(v,u);
}
}
en[u]=tim;
}
void ad(int x,ll c)
{
for(rint i=x;i<=n;i+=(i&(-i)))
{
s[i]+=c;
}
}
ll sum(int x)
{
ll ans=0;
for(rint i=x;i;i-=(i&(-i)))
{
ans+=s[i];
}
return ans;
}
int main()
{
n=read(),m=read(),R=read();
for(rint i=1;i<=n;++i)
{
d[i]=read();
}
for(rint i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y);
add(y,x);
}
dfs(R,0);
for(rint i=1;i<=m;++i)
{
int op=read();
if(op==1)
{
int x=read();
ll y=read();
ad(be[x],y);//差分思想
ad(en[x]+1,-y);
}
if(op==2)
{
int x=read();
ll ans=0;
ans+=sum(be[x]);//这里sum加的是修改了多少,仅仅指修改,在程序中,对原数据不做修改
ans+=d[x];//记得加上原数据
printf("%lld\n",ans);
}
}
return 0;
}

六、子树修改,子树查询

题意:有一棵树,有点权。进行n次子树权修改,也就是子树上每一个点的权都加v,n次提问以某子树的权值和。

还是那句话,子树在dfs序上是连续的区间,子树修改子树查询就相当于区间修改区间查询,线段树或树状数组维护即可,很水。

直接上代码(线段树)

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
#define p1 p<<1
#define p2 p<<1|1
using namespace std;
const int N=1e6+5;
int n,m,root,cnt,tim;
int h[N],be[N],en[N],dfn[N];
ll d[N];
struct edge
{
int v,nxt;
} e[N<<1];
struct tree
{
ll len,dat,laz;
} t[N<<2];
inline ll read()
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u,int fat)
{
be[u]=++tim;
dfn[tim]=u;
for(rint i=h[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v!=fat)
{
dfs(v,u);
}
}
en[u]=tim;
}
inline void update(int p)
{
t[p].dat=t[p1].dat+t[p2].dat;
}
inline void lazy(int p,ll v)//打懒标记
{
t[p].laz+=v;
t[p].dat+=(v*t[p].len);
}
inline void pushdown(int p)//懒标记下传
{
if(t[p].laz)
{
lazy(p1,t[p].laz);
lazy(p2,t[p].laz);
t[p].laz=0;
}
}
void build(int p,int l,int r)//建树
{
t[p].len=r-l+1;
if(l==r)
{
t[p].dat=d[dfn[l]];
t[p].laz=0;
return;
}
int mid=l+r>>1;
build(p1,l,mid);
build(p2,mid+1,r);
update(p);
}
void change(int p,int l,int r,int lr,int rr,ll v)//线段树修改
{
if(lr<=l&&r<=rr)
{
lazy(p,v);
return;
}
pushdown(p);
int mid=l+r>>1;
if(lr<=mid) change(p1,l,mid,lr,rr,v);
if(rr>mid) change(p2,mid+1,r,lr,rr,v);
update(p);
}
ll query(int p,int l,int r,int lr,int rr)//查询
{
if(lr<=l&&r<=rr)
{
return t[p].dat;
}
pushdown(p);
int mid=l+r>>1;
ll ans=0;
if(lr<=mid) ans+=query(p1,l,mid,lr,rr);
if(rr>mid) ans+=query(p2,mid+1,r,lr,rr);
return ans;
}
int main()
{
n=read(),m=read(),root=read();
for(rint i=1;i<=n;++i)
{
d[i]=read();
}
for(rint i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y);
add(y,x);
}
dfs(root,0);
build(1,1,tim);
for(rint i=1;i<=m;++i)
{
int op=read();
if(op==1)
{
int x=read();
ll y=read();
change(1,1,n,be[x],en[x],y);//区间修改
}
if(op==2)
{
int x=read();
printf("%lld\n",query(1,1,n,be[x],en[x]));//区间查询
}
}
return 0;
}

七、子树修改,链查询

题意:有一棵树,有点权。进行n次子树权修改,也就是子树上每一个点的权都加v,n次提问以某链的权值和。

还是贡献,链的查询,还是将链进行分解,这样就变成了(x->root)的权值和。

当修改子树的根节点y是x的祖先时,那么修改对(x->root)的权值和有贡献。贡献值为(deep[y]-deep[x]+1)*v=v*deep[y]-v*deep[x]+v 。deep[]可以预处理,这样只需要两个树状数组维护v*deep[]和v就可以了。

代码:

#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e6+5;
int n,m,root,cnt,tim;
int h[N],lg[N],be[N],en[N],st[N][20],dep[N];
ll d[N],s1[N],s2[N],size[N];
struct edge
{
int v,nxt;
} e[N<<1];
inline ll read()
{
ll x=0;
bool flag=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') flag=true;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return flag?~x+1:x;
}
void add_edge(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u,int fat)
{
dep[u]=dep[fat]+1;
st[u][0]=fat;
be[u]=++tim;
size[u]+=d[u];//记录到根节点的距离
for(rint i=1;i<=lg[dep[u]];++i)
st[u][i]=st[st[u][i-1]][i-1];
for(rint i=h[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v!=fat)
{
size[v]+=size[u];
dfs(v,u);
}
}
en[u]=tim;
}
void add1(int x,ll c)//处理s1 记录v的变化
{
if(x==0) return;
for(rint i=x;i<=n;i+=(i&(-i)))
s1[i]+=c;
}
void add2(int x,ll c)//处理s2 记录dep的乘积
{
if(x==0) return;
for(rint i=x;i<=n;i+=(i&(-i)))
s2[i]+=c;
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) x=x^y,y=x^y,x=x^y;
while(dep[x]>dep[y]) x=st[x][lg[dep[x]-dep[y]]-1];
if(x==y) return x;
for(rint i=lg[dep[x]]-1;i>=0;--i)
{
if(st[x][i]!=st[y][i])
{
x=st[x][i];
y=st[y][i];
}
}
return st[x][0];
}
ll sum1(int x)//求v的和
{
ll ans=0;
for(rint i=x;i;i-=(i&(-i)))
ans+=s1[i];
return ans;
}
ll sum2(int x)//求乘积的和
{
ll ans=0;
for(rint i=x;i;i-=(i&(-i)))
ans+=s2[i];
return ans;
}
int main()
{
n=read(),m=read(),root=read();
for(rint i=1;i<=n;++i)
d[i]=read();
for(rint i=1;i<n;++i)
{
int x=read(),y=read();
add_edge(x,y);
add_edge(y,x);
}
for(rint i=1;i<=n;++i)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
dfs(root,0);
for(rint i=1;i<=m;++i)
{
int op=read();//(deep[y]-deep[x]+1)*v=v*(deep[y]+1)-v*deep[x]
if(op==1)
{
int x=read();ll w=read();
add1(be[x],w);add1(en[x]+1,-w);//差分记录v
add2(be[x],dep[x]*w);add2(en[x]+1,-dep[x]*w);//差分记录dep*v
//这里记录的是要预处理的部分
}
if(op==2)
{
int a=read(),b=read(),lca=LCA(a,b),k=st[lca][0];
ll ans=size[a]+size[b]-size[lca]-size[k];//先加上原数据
ans+=((dep[a]+1)*sum1(be[a])-sum2(be[a]));
ans+=((dep[b]+1)*sum1(be[b])-sum2(be[b]));
ans-=((dep[lca]+1)*sum1(be[lca])-sum2(be[lca]));
ans-=((dep[k]+1)*sum1(be[k])-sum2(be[k]));
printf("%lld\n",ans);
//(dep[a]+1)*sum1(be[a]): v*(deep[y]+1)
//-sum2(be[a]): -v*deep[x]
}
}
}

DFS序和7种模型的更多相关文章

  1. 背单词(AC自动机+线段树+dp+dfs序)

    G. 背单词 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较   题目描述 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使 ...

  2. R - Weak Pair HDU - 5877 离散化+权值线段树+dfs序 区间种类数

    R - Weak Pair HDU - 5877 离散化+权值线段树 这个题目的初步想法,首先用dfs序建一颗树,然后判断对于每一个节点进行遍历,判断他的子节点和他相乘是不是小于等于k, 这么暴力的算 ...

  3. BZOJ 3083: 遥远的国度 [树链剖分 DFS序 LCA]

    3083: 遥远的国度 Time Limit: 10 Sec  Memory Limit: 1280 MBSubmit: 3127  Solved: 795[Submit][Status][Discu ...

  4. BZOJ 4196: [Noi2015]软件包管理器 [树链剖分 DFS序]

    4196: [Noi2015]软件包管理器 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1352  Solved: 780[Submit][Stat ...

  5. 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序

    3779: 重组病毒 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 224  Solved: 95[Submit][Status][Discuss] ...

  6. 【BZOJ-1146】网络管理Network DFS序 + 带修主席树

    1146: [CTSC2008]网络管理Network Time Limit: 50 Sec  Memory Limit: 162 MBSubmit: 3495  Solved: 1032[Submi ...

  7. 【BZOJ-3881】Divljak AC自动机fail树 + 树链剖分+ 树状数组 + DFS序

    3881: [Coci2015]Divljak Time Limit: 20 Sec  Memory Limit: 768 MBSubmit: 508  Solved: 158[Submit][Sta ...

  8. DFS序+线段树+bitset CF 620E New Year Tree(圣诞树)

    题目链接 题意: 一棵以1为根的树,树上每个节点有颜色标记(<=60),有两种操作: 1. 可以把某个节点的子树的节点(包括本身)都改成某种颜色 2. 查询某个节点的子树上(包括本身)有多少个不 ...

  9. Educational Codeforces Round 6 E dfs序+线段树

    题意:给出一颗有根树的构造和一开始每个点的颜色 有两种操作 1 : 给定点的子树群体涂色 2 : 求给定点的子树中有多少种颜色 比较容易想到dfs序+线段树去做 dfs序是很久以前看的bilibili ...

随机推荐

  1. java实现空心金字塔

    前言 最近在学习java,遇到了一个经典打印题目,空心金字塔,初学者记录,根据网上教程,有一句话感觉很好,就是先把麻烦的问题转换成很多的简单问题,最后一一解决就可以了,然后先死后活,先把程序写死,后面 ...

  2. 解读先电2.4版 iaas-install-mysql.sh 脚本

    #!/bin/bash #声明解释器路径 source /etc/xiandian/openrc.sh #生效环境变量 ping $HOST_IP -c 4 >> /dev/null 2& ...

  3. 一文彻底搞懂JavaScript中的prototype

    prototype初步认识 在学习JavaScript中,遇到了prototype,经过一番了解,知道它是可以进行动态扩展的 function Func(){}; var func1 = new Fu ...

  4. 基于DEM的坡度坡向分析

    坡度坡向分析方法 坡度(slope)是地面特定区域高度变化比率的量度.坡度的表示方法有百分比法.度数法.密位法和分数法四种,其中以百分比法和度数法较为常用.本文计算的为坡度百分比数据.如当角度为45度 ...

  5. vscode编写的程序中文乱码怎么办?

    (以下教程在源码文件的编码是utf-8的基础上进行!) (dev的源码文件是GBK编码,或者是GB2312?我现在好久没用dev,关于dev的信息可能有错误. 如果拿dev编写的代码用vscode打开 ...

  6. Python技法:用argparse模块解析命令行选项

    1. 用argparse模块解析命令行选项 我们在上一篇博客<Linux:可执行程序的Shell传参格式规范>中介绍了Linux系统Shell命令行下可执行程序应该遵守的传参规范(包括了各 ...

  7. Spark——Standalone 环境安装及简单使用

    Standalone 环境安装 将 spark-3.0.0-bin-hadoop3.2.tgz 文件解压缩在指定位置(/opt/module) tar -zxvf spark-3.0.0-bin-ha ...

  8. 场景实践:基于 IntelliJ IDEA 插件部署微服务应用

    体验简介 阿里云云起实验室提供相关实验资源,点击前往 本场景指导您把微服务应用部署到 SAE 平台: 登陆 SAE 控制台,基于 jar 包创建应用 基于 IntelliJ IDEA 插件更新 SAE ...

  9. Egg上层框架CabloyJS是如何输出SQL语句日志的?

    背景 在Egg开发实践中,经常会遇到一个问题:如何查看刚刚执行过的Egg组装的原生SQL语句呢? 1. 现有方案 可以直接在项目的config配置文件中添加MySQL配置debug: true.这会启 ...

  10. UVA471 Magic Numbers 题解

    1.题目 题意很简单:输入n,枚举所有的a,b,使得 (1)满足a/b=n. (2)满足a,b各个位上的数字不相同. 2.思路 (1)对于被除数,要满足各个位上的数字,显然最大枚举到987654321 ...