Loading

「学习笔记」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\) 种典型的模型

一、点修改,子树和查询

题目传送门
大体意思:有一棵树,有点权,进行 \(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;
}

二、三、(题目在一块儿,所以放一块讲)树链修改,点查询,子树和查询

题目传送门:
有一棵树,有点权,进行 \(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 \times 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) \times v\)\(v\) 这里是修改值),我们将式子拆开,就变成了 \(deep_y \times v-(deep_x-1) \times v\)\(deep_y\)\(deep_x\) 是可知的, \(v\) 在输入后也是可知的,但对于一整棵子树,公式就变成了 \(\sum deep_y \times v-(deep_x-1) \times v,deep_y \times 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\) ,所以点修改就转化成了子树修改,子树在 \(\text{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) \times v = v \times deep_y - v \times deep_x + v\)\(deep\) 可以预处理,这样只需要两个树状数组维护 \(v \times 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]
        }
    }
}
posted @ 2022-07-06 20:01  yi_fan0305  阅读(89)  评论(0编辑  收藏  举报