题目链接:https://nanti.jisuanke.com/t/31451
我参考的博客:https://blog.csdn.net/hao_zong_yin/article/details/82562210
这题写了两次,第二次写,我居然对这题没有一点印象,完全不知道以前写过......
第二次的代码用了分块和二分,加上一堆vector。
题意:给出一棵有n个点的树,每个点的初始值都是0,m次查询,如果op==1,那么后面接两个数字,表示把第a层的点的值增加b,如果op==2,那么后面接一个数a,表示查询以点a为根节点的这颗子树的所有点的值的和,输出这个值。
思路:因为n,m都可能达到1e5,因为要查找一棵树所有点的和,并且还要修改,如果用树结构查找肯定不方便,那么我们可以用DFS序把树转化为线性结构,用每个点的DSF序来表示这个点,那么查找的时候一棵子树就是一个连续区间,那么我们就可以用树状数组来查找一段区间的和,如果只用树状数组的单点更新,那么极端情况下某一层的点数非常多,更新就会超时;为了解决某一层点非常多并且要更新的情况,我们就可以使用分块的思想来减少更新所需的时间。把所有的层分成大块(点非常多)和小块(点比较少),如果是小块,我们就用树状数组来处理它,更新时暴力更新这一层所有点。如果是大块,我们就用标记数组来记录该层增加的值,因为我们用树的DFS序来代表点,并且DFS序是升序的的,我们就可以用二分查找每个大块来计算目标子树的和。
第一次看了博客的代码:
#include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> using namespace std; typedef long long LL; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 100005 /*struct point{ int u,w; }; bool operator <(const point &s1,const point &s2) { if(s1.w!=s2.w) return s1.w>s2.w; else return s1.u>s2.u; }*/ struct node{ int v,next; }edge[maxn*2]; int head[maxn],L[maxn],R[maxn],pre[maxn];//L[i]和R[i]数组表示i点DFS序的左右边界,pre[i]数组记录i的父亲,这里用来判断有没有走过 LL c[maxn],up[maxn];//c数组就是树状数组,up数组用来记录点数很多的层数(大块)的增加值 int n,m,k,r,cnt,Time,max_deep,block;//Time就是DFS序,max_deep记录最大的层数,block用来分块 vector<int>ve[maxn];//ve[deep]记录在第deep层的点 vector<int>vv; //vv存大块所在的层 void init() { fill(head,head+maxn-1,-1); fill(pre,pre+maxn-1,0); fill(c,c+maxn-1,0); fill(up,up+maxn-1,0); for(int i=0;i<=n;i++) ve[i].clear(); vv.clear(); Time=cnt=max_deep=0; block=sqrt(n); } void DFS(int u,int deep) { L[u]=++Time;//记录左边界 max_deep=max(max_deep,deep); ve[deep].push_back(Time);//把u点的DFS序值存入第deep层,我们用DFS序把把树转化为线性,然后用树状数组求区间和 for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(pre[v]) continue; pre[v]=u; DFS(v,deep+1); } R[u]=Time;//存右边界,注意Time不要加,因为一个点不用记录多个DFS序 } void add(int u,int v) { edge[++cnt].v=v; edge[cnt].next=head[u]; head[u]=cnt; } int lowbit(int x) { return x&(-x); } void Add(int x,int b)//树状数组单点更新 { for(int i=x;i<=n;i+=lowbit(i)) c[i]+=b; } LL sum(int x)//树状数组求区间和 { if(x<=0) return 0; LL ans=0; while(x>0) { ans+=c[x]; x-=lowbit(x); } return ans; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } pre[1]=-1; DFS(1,0);//u,depth for(int i=0;i<=max_deep;i++)//把大块存进vv数组 { if(ve[i].size()>block) vv.push_back(i); } int op; for(int i=1;i<=m;i++) { scanf("%d",&op); if(op==1) { int a,b; scanf("%d%d",&a,&b); if(ve[a].size()>block)//如果a层是大块 { up[a]+=b; } else//a层不是大块 { for(int i=0;i<ve[a].size();i++)//暴力更新每个点 Add(ve[a][i],b); } } else { int a; scanf("%d",&a); int l=L[a],r=R[a]; LL ans=sum(r)-sum(l-1);//我们在树状数组里存的是DFS序,求和是根据DFS序来求和 for(int i=0;i<vv.size();i++)//遍历所有大块来寻找属于子树的值 { int idx=vv[i]; ans+=(upper_bound(ve[idx].begin(),ve[idx].end(),r)-lower_bound(ve[idx].begin(),ve[idx].end(),l))*up[idx]; } printf("%lld ",ans); } } } return 0; }
第二次代码:
也是分块,但是用vector做了一些有点麻烦的操作
#include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<map> #include<stack> #include<cmath> #include<vector> #include<set> #include<cstdio> #include<string> #include<deque> using namespace std; typedef long long LL; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 100005 struct node{ int v,next; }edge[maxn*2]; vector<int>dep[maxn],ve; int n,m,k,t,cnt,block,Time; int L[maxn],R[maxn],zu[maxn],de[maxn],head[maxn]; //L和R数组存的是第一次和最后一次到达i的时间,其中L[i]也带比奥i的编号 //这里用来分块,所以zu[i]代表编号i所在的块,de[i]代表编号i对应的深度 LL a[maxn],sum[maxn],tag[maxn]; //a[i]表示i这个编号上的点权,sum[i]表示第i个块上的和(只包括同一深度下点数小于block的) //tag[i]表示深度i上的点要加的值 void init(){ memset(head,-1,sizeof(head)); memset(tag,0,sizeof(tag)); memset(sum,0,sizeof(sum)); for(int i=0;i<=n;i++) dep[i].clear(); ve.clear(); cnt=0,Time=0; block=sqrt(n); memset(a,0,sizeof(a)); for(int i=1;i<=n;i++){ zu[i]=(i-1)/block+1; } } void add(int u,int v){ edge[++cnt].v=v; edge[cnt].next=head[u]; head[u]=cnt; } void DFS(int u,int depth,int pre){ L[u]=++Time;//记录u第一次到达的时间 ,当成u的编号,之后只用u的编号了 dep[depth].push_back(Time);//u的编号在深度为depth的编号集合中 ,把相同深度的编号放在一个集合中 de[Time]=depth;//保存一下编号Time的深度 ,de代表编号的深度 for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(v==pre) continue; DFS(v,depth+1,u); } R[u]=Time;//右边界,L和R就是一个线性的区间 } void change(int x,int b){//暴力更新深度为x的所有点上的值 for(int i=0;i<dep[x].size();i++){ a[dep[x][i]]+=b; sum[zu[dep[x][i]]]+=b;//dep[x][i]这个编号所在的块的和也要改变一下 } } LL ask(int l,int r){ LL ans=0; //下面的三个for循环是分块板子,把查询区间中所有点数不超过block的深度 的所有点权相加 for(int i=l;i<=min(zu[l]*block,r);i++){ ans+=a[i]; } for(int i=zu[l]+1;i<=zu[r]-1;i++){ ans+=sum[i]; } if(zu[l]!=zu[r]){ for(int i=(zu[r]-1)*block+1;i<=r;i++){ ans+=a[i]; } } //下面用了二分,对于每一个ve里面的深度,我们查找这个深度中编号在区间[l,r]中的点数 for(int i=0;i<ve.size();i++){ int x=ve[i];//代表一个深度,这个深度所包含的点的数量大于等于block int num=upper_bound(dep[x].begin(),dep[x].end(),r)-lower_bound(dep[x].begin(),dep[x].end(),l); ans+=num*tag[x];//点数乘以这个深度所标记的值 } return ans; } int main() { while(scanf("%d%d",&n,&m)!=EOF){ init(); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v); add(v,u); } DFS(1,0,0); for(int i=0;i<n;i++){//假如这个深度所包含的编号数量大于block,我们把它保存在ve中 if(dep[i].size()>=block) ve.push_back(i); } /* for(int i=0;i<ve.size();i++) sort(dep[ve[i]].begin(),dep[ve[i]].end());*///不用排序,因为编号就是从小到大的 int op,a,b; for(int i=0;i<m;i++){ scanf("%d",&op); if(op==1){ scanf("%d%d",&a,&b); if(dep[a].size()<block){ change(a,b);//这个深度的点的个数小于block个,暴力更新 }else{ tag[a]+=b;//这个深度的点的个数大于等于block个,标记一下 } }else{ scanf("%d",&a); printf("%lld ",ask(L[a],R[a]));//查询一个区间 } } } return 0; }