应该算分块的好题,因为我没想到
题目
求树上 ∑ i = l r s i \sum_{i=l}^rs_i ∑i=lrsi,其中 s i s_i si表示子树权值和,带修
题解
O
(
n
n
)
O(n\sqrt n)
O(nn)
分块,一个维护原标号,一个维护
d
f
n
dfn
dfn序
修改时,更改第一种块需要用到
d
p
i
,
k
dp_{i,k}
dpi,k表示
i
i
i标号会在第
k
k
k块中计算几次。用第二种块维护散点权值,将原本单点修改变为区间修改单点查,依次做到之后查询
O
(
1
)
O(1)
O(1)
查询时,第一种块爆加,散块在第二种块里查
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10,M=2*N,K=4e2+10;
int n,q;
int head[N],nex[M],to[M],tot;
void build(int u,int v){tot++;nex[tot]=head[u];to[tot]=v;head[u]=tot;}
//
int g,sq;
int bl[N],L[N],R[N];
ll a[N],s[N],b[N];
ll ak[K],bk[K];
int dp[N][K];
int dfn[N],sz[N],cnt;
void init()
{
sq=sqrt(n);
for(int i=1;i<=n;i++)
{
if((i-1)%sq==0)R[g]=i-1,L[++g]=i;
bl[i]=g;
}R[g]=n;
}
void add(int l,int r,ll key)
{
int x=bl[l],y=bl[r];
if(y-x<=2){for(int i=l;i<=r;i++)b[i]+=key;return ;}
for(int i=l;i<=R[x];i++)b[i]+=key;x++;
for(int i=L[y];i<=r;i++)b[i]+=key;y--;
for(int i=x;i<=y;i++)bk[i]+=key;
}
ll qur(int u)
{
int x=dfn[u]-1,y=dfn[u]+sz[u]-1;
return (b[y]+bk[bl[y]])-(b[x]+bk[bl[x]]);
}
ll qur2(int l,int r)
{
int x=bl[l],y=bl[r];ll tmp=0;
if(y-x<=2){for(int i=l;i<=r;i++)tmp+=qur(i);return tmp;}
for(int i=l;i<=R[x];i++)tmp+=qur(i);x++;
for(int i=L[y];i<=r;i++)tmp+=qur(i);y--;
for(int i=x;i<=y;i++)tmp+=ak[i];
return tmp;
}
void dfs(int u,int f)
{
dfn[u]=++cnt;b[cnt]=a[u];sz[u]=1;s[u]=a[u];
for(int i=1;i<=g;i++)dp[u][i]=dp[f][i]+(L[i]<=u&&u<=R[i]);
for(int i=head[u];i;i=nex[i])
{
int v=to[i];if(v==f)continue;
dfs(v,u);sz[u]+=sz[v];s[u]+=s[v];
}ak[bl[u]]+=s[u];
}
signed main()
{
scanf("%d%d",&n,&q);n++;
init();
for(int i=2;i<=n;i++)scanf("%d",&a[i]);
for(int x,y,i=1;i<n;i++)
{
scanf("%d%d",&x,&y);x++;y++;
build(x,y);build(y,x);
}
dfs(1,0);
for(int i=1;i<=n;i++)b[i]+=b[i-1];
for(int op,x,y,i=1;i<=q;i++)
{
scanf("%d%d%d",&op,&x,&y);x++;
if(op==1)
{
for(int j=1;j<=g;j++)ak[j]+=(ll)dp[x][j]*(y-a[x]);
add(dfn[x],cnt,(ll)y-a[x]);
a[x]=y;
}
else printf("%lld\n",qur2(x,y+1));
}
}