bzoj 4765: 普通计算姬 分块+树状数组

Description

“奋战三星期,造台计算机”。小G响应号召,花了三小时造了台普通计算姬。普通计算姬比普通计算机要厉害一些
。普通计算机能计算数列区间和,而普通计算姬能计算树中子树和。更具体地,小G的计算姬可以解决这么个问题
:给定一棵n个节点的带权树,节点编号为1到n,以root为根,设sum[p]表示以点p为根的这棵子树中所有节点的权
值和。计算姬支持下列两种操作:
1 给定两个整数u,v,修改点u的权值为v。
2 给定两个整数l,r,计算sum[l]+sum[l+1]+…+sum[r-1]+sum[r]
尽管计算姬可以很快完成这个问题,可是小G并不知道它的答案是否正确,你能帮助他吗?
Input

第一行两个整数n,m,表示树的节点数与操作次数。
接下来一行n个整数,第i个整数di表示点i的初始权值。
接下来n行每行两个整数ai,bi,表示一条树上的边,若ai=0则说明bi是根。
接下来m行每行三个整数,第一个整数op表示操作类型。
若op=1则接下来两个整数u,v表示将点u的权值修改为v。
若op=2则接下来两个整数l,r表示询问。
N &lt; = 1 0 5 M &lt; = 1 0 5 N&lt;=10^5 M&lt;=10^5 N<=105M<=105
0 &lt; = D i , V &lt; 2 31 , 1 &lt; = L &lt; = R &lt; = N , 1 &lt; = U &lt; = N 0&lt;=Di,V&lt;2^{31},1&lt;=L&lt;=R&lt;=N,1&lt;=U&lt;=N 0<=Di,V<231,1<=L<=R<=N,1<=U<=N
Output

对每个操作类型2输出一行一个整数表示答案。
Sample Input

6 4

0 0 3 4 0 1

0 1

1 2

2 3

2 4

3 5

5 6

2 1 2

1 1 1

2 3 6

2 3 5
Sample Output

16

10

9

分析:
我们设 s [ i ] s[i] s[i]表示以 i i i为根的子树的权值和,这个可以用树状数组去维护。
但是题目要求 ∑ i = l r s [ i ] \sum_{i=l}^{r}s[i] i=lrs[i],显然枚举一遍跑树状数组是不理智的,我们可以预处理一些东西。
我们可以分块维护这个和。对于一次修改操作,我们要修改每一个块,而这个块修改的权值有一个系数,这个系数是修改点的父亲(包括自己)在这个块内的个数,这个可以dfs时候做出来。然后顺带修改一下树状数组。
那么对于询问,整块内的已经预处理了,散块直接树状数组暴力求即可。
一开始边数没有乘 2 2 2,然后还有这题要开unsigned long long

代码:

/**************************************************************
    Problem: 4765
    User: liangzihao
    Language: C++
    Result: Accepted
    Time:9164 ms
    Memory:147724 kb
****************************************************************/
 
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL unsigned long long
  
const int maxn=1e5+7;
  
using namespace std;
  
int n,m,root,cnt,x,y,num,op;
int ls[maxn],a[maxn],dfn[maxn],last[maxn],f[maxn][350],l[maxn],r[maxn],bel[maxn];
LL c[maxn],sum[350];
  
struct edge{
    int y,next;
}g[maxn*2];
  
void add(int x,int y)
{
    g[++cnt]=(edge){y,ls[x]};
    ls[x]=cnt;
}
  
void dfs(int x,int fa)
{
    dfn[x]=last[x]=++cnt;
    for (int i=1;i<=num;i++) f[x][i]=f[fa][i];
    f[x][bel[x]]++;
    for (int i=ls[x];i>0;i=g[i].next)
    {
        int y=g[i].y;
        if (y==fa) continue;
        dfs(y,x);
        last[x]=max(last[x],last[y]);
    }
}
  
void updata(int x,int k)
{
    for (int i=x;i<=n;i+=i&(-i)) c[i]+=(LL)k;
}
  
LL calc(int x)
{
    LL sum=0;
    for (int i=x;i>0;i-=i&(-i)) sum+=c[i];
    return sum;
}
  
LL getsum(int l,int r)
{
    return calc(r)-calc(l-1);
}
  
void build_block()
{
    int block=trunc(sqrt(n));
    num=n/block+(n%block!=0);
    for (int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;
    for (int i=1;i<=n;i++) bel[i]=(i-1)/block+1;
}
  
void change(int x,int k)
{
    updata(dfn[x],k);
    for (int i=1;i<=num;i++) sum[i]+=(LL)f[x][i]*k;
}
  
LL query(int x,int y)
{
    LL ans=0;
    if (bel[x]==bel[y])
    {
        for (int i=x;i<=y;i++) ans+=getsum(dfn[i],last[i]);
    }
    else
    {
        for (int i=bel[x]+1;i<=bel[y]-1;i++) ans+=sum[i];
        for (int i=x;i<=r[bel[x]];i++) ans+=getsum(dfn[i],last[i]);
        for (int i=l[bel[y]];i<=y;i++) ans+=getsum(dfn[i],last[i]);
    }
    return ans;
}
  
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        if (x)
        {
            add(x,y);
            add(y,x);
        }
        else root=y;
    }   
    build_block();
    cnt=0;   
    dfs(root,0); 
    for (int i=1;i<=n;i++) change(i,a[i]);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&op,&x,&y);
        if (op==1)
        {
            change(x,y-a[x]);
            a[x]=y;
        }
        else printf("%llu\n",query(x,y));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值