洛谷U3981 语文 DFS序+线段树

题目大意:对于一个有根树有n个结点,每个结点上都有权值。一共有q次操作,分别为以下两种:
1.将以结点i为根的子树除i之外的点的重要度增加delta
2.询问当前点重要度。
数据范围:n<=5*10^5,q<=10^6,delta<=10^3
对于30%的数据n<=500,q<=1000.

对于30%的数据,直接模拟在树上的修改即可,最坏情况单次修改达到O(n)
本题中,要修改一棵子树上的所有值,于是想到与区间有关的问题。如何把树转为区间呢?可以先预处理,DFS遍历这棵树,记录遍历序,这样就转成区间了。因为要修改子树上的值,于是可以记录入栈序,这样处理就可以把一个结点子树变成一段连续的区间。区间修改单点查询用树状数组或线段树均可。

#include <cstdio>
#include <cstring>
using namespace std;
struct Edge{
    int from,to,nex;
}e[500010];
struct Segment_Tree{
    int l,r,add,v;
}st[2000010];
struct Node{
    int val,pos,l,r;
}a[500010];
int n,fir[500010],m,sta[500010],top,pa[500010];
void init(int,int,int);
void dfs(int);
int query(int,int);
void push_down(int);
void change(int,int,int,int);
int root(int);
int main(){
    freopen("chinese.in","r",stdin);
    freopen("chinese.out","w",stdout);
    scanf("%d%d",&n,&m);
    memset(fir,-1,sizeof fir);
    scanf("%d",&a[1].val);
    for(int i=1;i<=n;i++) pa[i]=i;
    for(int i=2;i<=n;i++) {
        int x;
        scanf("%d%d",&a[i].val,&x);
        pa[i]=root(x);
        e[i].from=x; e[i].to=i;
        e[i].nex=fir[x]; fir[x]=i;
    }
    dfs(1);
    init(1,1,n);
    while(m--){
        char mode[3];
        int x,y;
        scanf("%s",mode);
        scanf("%d",&x);
        if(mode[0]=='u') {
            if(root(x)==1) printf("%d\n",query(1,a[x].pos));
            else printf("0\n");
        }
        else { scanf("%d",&y); change(1,a[x].l,a[x].r,y); }
    }
    fclose(stdin); fclose(stdout);
    return 0;
}
void init(int x,int l,int r){
    st[x].l=l; st[x].r=r;
    if(l==r) { st[x].v=a[sta[l]].val; return; }
    int mid=(l+r)>>1;
    init(x<<1,l,mid); init((x<<1)+1,mid+1,r);
    return ;
}
void dfs(int x){
    sta[++top]=x;
    a[x].pos=top;
    a[x].l=top+1;
    for(int i=fir[x];i!=-1;i=e[i].nex) dfs(e[i].to);
    a[x].r=top;
    return;
}
int query(int x,int pos){
    if(st[x].l==pos && st[x].r==pos) return st[x].v+st[x].add;
    if(st[x].add) push_down(x); 
    int mid=(st[x].l+st[x].r)>>1;
    if(pos<=mid) return query(x<<1,pos);
    return query((x<<1)+1,pos);
}
void push_down(int x){
    st[x<<1].add+=st[x].add;
    st[(x<<1)+1].add+=st[x].add;
    st[x].add=0;
    return;
}
void change(int x,int l,int r,int v){
    if(l>r) return;
    if(st[x].add && st[x].l!=st[x].r) push_down(x);
    if(st[x].l==l && st[x].r==r) { st[x].add+=v; return; }
    int mid=(st[x].l+st[x].r)>>1;
    if(r<=mid) change(x<<1,l,r,v);
    else if(l>mid) change((x<<1)+1,l,r,v);
    else change(x<<1,l,mid,v),change((x<<1)+1,mid+1,r,v);
    return; 
}
int root(int x){return pa[x]==x ? pa[x] : root(pa[x]); }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值