22/7/22!

1,一个简单的整数问题2;2,树链剖分;


1,一个简单的整数问题2;

主要练习懒标记的使用,简单的pushup是向上传递,因为线段树的叶子节点都是单个数,在最下面,所以单点修改只能找到最下面然后改掉,往上pushup值的改变以及带来的影响;

但是区间修改的话,如果对修改区间的每个点都像单点修改那样找到每个叶子节点在修改,复杂度还是会爆炸,所以就要在上面的代表区间的节点进行修改,只有修改到了这一层,就是从上往下传递修改,对应的就是modify中,要找左右儿子时,往下传递修改(懒标记);还有要传递修改的地方是在query查询查到该节点时,因为该值实际上要改变,所以懒标记就要传到这里;(记住两次pushdown的地方!!) 

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=(a);i<(n);++i) 
#define rep2(i,a,n) for( int i=(a);i<=(n);++i) 
#define per1(i,n,a) for( int i=(n);i>(a);i--) 
#define per2(i,n,a) for( int i=(n);i>=(a);i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define eb emplace_back
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define dbug(y) cout<<(#y)<<"\n"
#define yi first
#define er second
#define INF 0x3f3f3f3f
#define tulun int e[N],ne[N],h[N],w[N],idx;
#define add2(a,b) e[idx]=b,ne[idx]=h[a],h[a]=idx++;
#define add3(a,b,c) we[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
#define T_solve() int T;cin>>T;while(T--)solve();
#define pi 3.14159265358979323846
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<pair<int,int>,pair<int,int>>PIIII;
typedef pair<long long,long long> PLL;
typedef double dob;
typedef pair<dob,dob> PDD;
const int N=1e5+10;
int n,m;
struct node
{
    int l,r;
    LL sum,add;
}tr[N<<2];
int w[N];

void pushup(int u)
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)
    {
        left.sum+=(LL)(left.r-left.l+1)*root.add;
        left.add+=root.add;
        right.sum+=(LL)(right.r-right.l+1)*root.add;
        right.add+=root.add;
        root.add=0;
    }
}
void build(int u,int l,int r)
{
    tr[u]={l,r};
    if(l==r)
    {
        tr[u]={l,r,w[r]};
        return;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}
void modify(int u,int l,int r,int d)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].sum+=(LL)(tr[u].r-tr[u].l+1)*d;
        tr[u].add+=d;
        return;
    }
    int mid=tr[u].l+tr[u].r>>1;
    pushdown(u);
    if(l<=mid)modify(u<<1,l,r,d);
    if(r>mid)modify(u<<1|1,l,r,d);
    pushup(u);
}
LL query(int u,int l,int r)
{
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
    int mid=tr[u].l+tr[u].r>>1;
    pushdown(u);
    LL sum=0;
    if(l<=mid)sum=query(u<<1,l,r);
    if(r>mid)sum+=query(u<<1|1,l,r);
    return sum;
}
signed main()
{
    quick_cin();
    cin>>n>>m;
    rep2(i,1,n)cin>>w[i];
    build(1,1,n);
    while(m--)
    {
        char c;
        cin>>c;
        if(c=='Q')
        {
            int l,r;
            cin>>l>>r;
            cout<<query(1,l,r)<<endl;
        }
        else 
        {
            int l,r,d;
            cin>>l>>r>>d;
            modify(1,l,r,d);
        }
    }
    return 0;
}

2,树链剖分;

树链剖分的目的有两个:

1,将一颗树转化为一个序列;

2,将树中的路径转化为logn段连续区间;

 如此一来,对树上某一连续路径的操作就变成了区间修改,套线段树,树状数组,分块等都可以做了就;

分路径的依据就是重儿子以及重边;

通过重儿子不断往上爬最终得到u到v的路径;

 

 修改路径,就是通过重儿子爬山找;

void update_path(int u,int v,int k)
{
    while(top[u]!=top[v])//因为dfs序是从上往下搜,所以下面的编号和深度会大;
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        modify(1,id[top[u]],id[u],k);//top在上面,所以编号小,放在左端点;
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);//最终形成u在下面,v在上面的情形;
    modify(1,id[v],id[u],k);
}

修改子树,根据dfs序来,更新子树很简单,因为子树的dfs序一定是连续的,知道左端点是子树的根,有子树的大小sz,所以右端点就是id[u]+sz[u]-1;

void update_tree(int u,int k)
{
    modify(1,id[u],id[u]+sz[u]-1,k);//更新子树很简单,因为子树的dfs序一定是连续的,知道左端点是子树的根
    //有子树的大小,所以右端点就是id[u]+sz[u]-1;
}

 查询路径和修改路径一样,就是爬的时候把更新换成查询;

LL query_path(int u,int v)
{
    LL ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        ans+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    ans+=query(1,id[v],id[u]);
    return ans;
} 

 查询子树,和修改子树一样简单,因为是一段连续的区间,可以直接查询;

LL query_tree(int u)
{
    return query(1,id[u],id[u]+sz[u]-1);//左端点是根节点u,右端点是根节点编号加上子树大小-1
}
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=(a);i<(n);++i) 
#define rep2(i,a,n) for( int i=(a);i<=(n);++i) 
#define per1(i,n,a) for( int i=(n);i>(a);i--) 
#define per2(i,n,a) for( int i=(n);i>=(a);i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define eb emplace_back
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define dbug(y) cout<<(#y)<<"\n"
#define yi first
#define er second
#define INF 0x3f3f3f3f
#define tulun int e[N],ne[N],h[N],w[N],idx;
#define add2(a,b) e[idx]=b,ne[idx]=h[a],h[a]=idx++;
#define add3(a,b,c) we[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
#define T_solve() int T;cin>>T;while(T--)solve();
#define pi 3.14159265358979323846
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<pair<int,int>,pair<int,int>>PIIII;
typedef pair<long long,long long> PLL;
typedef double dob;
typedef pair<dob,dob> PDD;
const int N=2e5+10;
tulun;
int n,m;
int id[N],nw[N],cnt;
int sz[N],dep[N],fa[N],son[N],top[N];

struct node
{
    int l,r;
    LL add,sum;
}tr[N<<1];

void dfs1(int u,int father,int depth)//求重儿子;
{
    sz[u]=1,dep[u]=depth,fa[u]=father;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==father)continue;//防止往回搜;
        dfs1(j,u,depth+1);//直接递归遍历儿子,通过回溯完成下面的操作;
        sz[u]+=sz[j];//当前节点加上儿子节点的大小;
        if(sz[son[u]]<sz[j])son[u]=j;//如果重儿子的子树大小小于j的子树大小,说明j是重儿子;
    }
}
void dfs2(int u,int t)//求dfs序,t代表所在重链的顶点;
{
    id[u]=++cnt,nw[cnt]=w[u],top[u]=t;
    if(!son[u])return;
    dfs2(son[u],t);//优先遍历重儿子;
    for(int i=h[u];~i;i=ne[i])//然后遍历轻儿子;
    {
        int j=e[i];
        if(j==fa[u]||j==son[u])continue;
        dfs2(j,j);//轻儿子的顶点就是它自己;
    }
}
void pushup(int u)
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)
    {
        left.add+=root.add;
        left.sum+=(left.r-left.l+1)*root.add;
        right.add+=root.add;
        right.sum+=(right.r-right.l+1)*root.add;
        root.add=0;
    }
}
void build(int u,int l,int r)
{
    tr[u]={l,r};
    if(l==r)
    {
        tr[u].sum=nw[l];
        return;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}
void modify(int u,int l,int r,int d)
{
    if(l<=tr[u].l&&r>=tr[u].r)
    {
        tr[u].sum+=(tr[u].r-tr[u].l+1)*d;
        tr[u].add+=d;
        return;
    }
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)modify(u<<1,l,r,d);
    if(r>mid)modify(u<<1|1,l,r,d);
    pushup(u);
}
LL query(int u,int l,int r)
{
    if(l<=tr[u].l&&tr[u].r<=r)return tr[u].sum;
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    LL sum=0;
    if(l<=mid)sum+=query(u<<1,l,r);
    if(r>mid)sum+=query(u<<1|1,l,r);
    return sum;
}
void update_path(int u,int v,int k)
{
    while(top[u]!=top[v])//因为dfs序是从上往下搜,所以下面的编号和深度会大;
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        modify(1,id[top[u]],id[u],k);//top在上面,所以编号小,放在左端点;
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);//最终形成u在下面,v在上面的情形;
    modify(1,id[v],id[u],k);
}
LL query_path(int u,int v)
{
    LL ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        ans+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    ans+=query(1,id[v],id[u]);
    return ans;
}    

void update_tree(int u,int k)
{
    modify(1,id[u],id[u]+sz[u]-1,k);//更新子树很简单,因为子树的dfs序一定是连续的,知道左端点是子树的根
    //有子树的大小,所以右端点就是id[u]+sz[u]-1;
}
LL query_tree(int u)
{
    return query(1,id[u],id[u]+sz[u]-1);
}
signed main()
{
    quick_cin();
    memset(h,-1,h);
    cin>>n;
    rep2(i,1,n)cin>>w[i];
    rep2(i,1,n-1)
    {
        int a,b;
        cin>>a>>b;
        add2(a,b);
        add2(b,a);
    }
    dfs1(1,-1,1);
    dfs2(1,1);
    build(1,1,n);
    cin>>m;
    while(m--)
    {
        int t,u,v,k;
        cin>>t>>u;
        if(t==1)
        {
            cin>>v>>k;
            update_path(u,v,k);
        }
        else if(t==2)
        {
            cin>>k;
            update_tree(u,k);
        }
        else if(t==3)
        {
            cin>>v;
            cout<<query_path(u,v)<<endl;
        }
        else cout<<query_tree(u)<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dull丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值