BZOJ 4765 普通计算姬 dfs序+分块+树状数组+好题!!!
真是道好题。。。感到灵魂的升华。。。
按dfs序建树状数组,拿前缀和去求解散块;
按点的标号分块,分成一个个区间,记录区间子树和 的 总和。。。
具体地,需要记录每个点u修改后,对每一个块i的贡献,记为t[u][i]
计算思路:dfs时,每到一个新的点,就让++c[其所在块],为了记录每个块中的点出现过几次,就相当于记录这个点 被每一块中的点 包含了几次 , 然后for一遍,记录t[u][i]=c[i]
当修改一个点时,这个块的和+=这个点u对块i的贡献*这个点的变化量,即sum[i]+=t[u][i]*(v-a[u]);
#include<cstdio> #include<iostream> #include<cmath> #define ll unsigned long long #define R register ll using namespace std; const int N=100010; inline ll g() { R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix; } int n,m,q,T,cnt,num,rt; int vr[N<<1],nxt[N<<1],a[N],fir[N],dfn[N],l[N],r[N],c[320],t[N][320],pos[N]; ll w[N],sum[320]; inline void add(int u,int v) {vr[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt;} inline int lbt(int x) {return x&-x;} inline void inc(int pos,ll d) {for(;pos<=n;pos+=lbt(pos)) w[pos]+=d;} inline ll query(int pos) { R ret=0; for(;pos;pos-=lbt(pos)) ret+=w[pos]; return ret; } void dfs(int u) { l[u]=++num,++c[pos[u]]; inc(num,a[u]); for(R i=1;i<=pos[n];++i) t[u][i]=c[i]; for(R i=fir[u];i;i=nxt[i]) { R v=vr[i]; if(l[v]) continue; dfs(v); } r[u]=num,--c[pos[u]]; } signed main() { n=g(),q=g(); T=sqrt(n); m=(n%T)?n/T+1:n/T; for(R i=1;i<=n;++i) a[i]=g(); for(R i=1;i<=n;++i) pos[i]=(i-1)/T+1; for(R i=1,u,v;i<=n;++i) {u=g(),v=g(); if(!u) rt=v; else add(u,v),add(v,u);} dfs(rt); for(R i=1;i<=pos[n];++i) for(R j=(i-1)*T+1,lim=min(i*T,(ll)n);j<=lim;++j) sum[i]+=query(r[j])-query(l[j]-1); for(R i=1;i<=q;++i) { R k=g(),u=g(),v=g(); if(k&1) { inc(l[u],v-a[u]); for(R i=1;i<=pos[n];++i) sum[i]+=1ull*t[u][i]*(v-a[u]); a[u]=v; } else { R ret=0; if(pos[u]==pos[v]) for(R i=u;i<=v;++i) ret+=query(r[i])-query(l[i]-1); else { for(R i=u;i<=pos[u]*T;++i) ret+=query(r[i])-query(l[i]-1); for(R i=(pos[v]-1)*T+1;i<=v;++i) ret+=query(r[i])-query(l[i]-1); } for(R i=pos[u]+1;i<pos[v];++i) ret+=sum[i]; printf("%llu\n",ret); } } }
这题真神仙。。2019.04.23