DFS序和7种模型
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种模型的更多相关文章
- 背单词(AC自动机+线段树+dp+dfs序)
G. 背单词 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使 ...
- R - Weak Pair HDU - 5877 离散化+权值线段树+dfs序 区间种类数
R - Weak Pair HDU - 5877 离散化+权值线段树 这个题目的初步想法,首先用dfs序建一颗树,然后判断对于每一个节点进行遍历,判断他的子节点和他相乘是不是小于等于k, 这么暴力的算 ...
- BZOJ 3083: 遥远的国度 [树链剖分 DFS序 LCA]
3083: 遥远的国度 Time Limit: 10 Sec Memory Limit: 1280 MBSubmit: 3127 Solved: 795[Submit][Status][Discu ...
- BZOJ 4196: [Noi2015]软件包管理器 [树链剖分 DFS序]
4196: [Noi2015]软件包管理器 Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 1352 Solved: 780[Submit][Stat ...
- 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序
3779: 重组病毒 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 224 Solved: 95[Submit][Status][Discuss] ...
- 【BZOJ-1146】网络管理Network DFS序 + 带修主席树
1146: [CTSC2008]网络管理Network Time Limit: 50 Sec Memory Limit: 162 MBSubmit: 3495 Solved: 1032[Submi ...
- 【BZOJ-3881】Divljak AC自动机fail树 + 树链剖分+ 树状数组 + DFS序
3881: [Coci2015]Divljak Time Limit: 20 Sec Memory Limit: 768 MBSubmit: 508 Solved: 158[Submit][Sta ...
- DFS序+线段树+bitset CF 620E New Year Tree(圣诞树)
题目链接 题意: 一棵以1为根的树,树上每个节点有颜色标记(<=60),有两种操作: 1. 可以把某个节点的子树的节点(包括本身)都改成某种颜色 2. 查询某个节点的子树上(包括本身)有多少个不 ...
- Educational Codeforces Round 6 E dfs序+线段树
题意:给出一颗有根树的构造和一开始每个点的颜色 有两种操作 1 : 给定点的子树群体涂色 2 : 求给定点的子树中有多少种颜色 比较容易想到dfs序+线段树去做 dfs序是很久以前看的bilibili ...
随机推荐
- Docker被禁了!只能靠它了......
科技飞速发展的今天,企业对候选人有了新的更高要求,如市场.运营等必须会Python.Sql,面试常问诸如用户漏斗等考察数据分析能力.可以说,懂数据的人会更有竞争力通过面试. 而市场上,专业的数据分析人 ...
- Go Http Get 和 Post 工具函数
前言 先说一下为什么要搞这个小东西? 米攸服务端前期主要是基于 Go 构建的,版本迭代过程中,业务复杂度不断增加,再加上中员团队有人员变动,考虑到目前团队的技术背景,我们开始考虑把接口服务分批迁移到 ...
- 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析)
电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 目录 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 写在前面 正文 电机噪声 谐波的产生 什么 ...
- 使用 VS Code + Markdown 编写 PDF 文档
背景介绍 作为一个技术人员,基本都需要编写技术相关文档,而且大部分技术人员都应该掌握 markdown 这个技能,使用 markdown 来编写并生成 PDF 文档将会是一个不错的体验,以下就介绍下如 ...
- JavaMetaweblogClient,Metaweblog的java实现-从此上传博客实现全平台
目录 1. 什么是Metaweblog? 2. Metaweblog的应用 3. 如何使用Metaweblog 4. 本项目介绍 4.1 metaweblog与java之间的关系映射 4.2 使用Ja ...
- Python的.gitignore模板
参考:https://github.com/github/gitignore Python的.gitignore模板,记录一下方便查询 # Byte-compiled / optimized / DL ...
- 魔改了一下bootstrap-treeview组件,发布个NPM包体验一下
前言 之前在这篇文章 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示 中说到,我为了让文章分类列表支持层级结构,用了一个树形组件,不过这个组件太老了,使用的Boots ...
- jeecgboot-vue3笔记(三)弹窗的使用
需求描述 点击按钮,弹窗窗体(子组件),确定后在子组件中完成业务逻辑处理(例如添加记录),然后回调父组件刷新以显示最近记录. 实现步骤 子组件 子组件定义BasicModal <BasicMod ...
- 关于Vue在面试中常常被提到的几点(持续更新……)
1.Vue项目中为什么要在列表组件中写key,作用是什么? 我们在业务组件中,会经常使用循环列表,当时用v-for命令时,会在后面写上:key,那么为什么建议写呢? key的作用是更新组件时判断两个节 ...
- Vue.js与Node.js一起打造一款属于自己的音乐App(收藏)
更多内容请见原文,原文转载自:https://blog.csdn.net/weixin_44519496/article/details/118755888