ACM-ICPC 2018 沈阳赛区网络预赛 J. Ka Chang

题目链接: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;
}
原文地址:https://www.cnblogs.com/6262369sss/p/9732675.html