ACM-ICPC 2018 沈阳赛区网络预赛 J. Ka Chang (分块+dfs序+树状数组)

题目链接

题意:

给你一颗n个节点的树,每一个节点的值为0。

q种操作,1.将深度为L的点的权值加x,根节点的深度为0

2.输出以x为根节点的子树的权值和

解析:

这道题最后想到了分块,因为我想到用的一种方法需要用[1e5][1e5]的空间去维护,有空间问题,

然后借此想到分块可以处理,小的部分暴力,大的部分用数组保存状态,这样就存的下了

不过我想的是按层来分块,前block层,和后block层,最后发现并没有什么用

搜了题解,是按照每一层内的点的个数来分类的,因为最暴力的做法,就是每一次修改,

把该层每一个点都更新一遍,所以我们就将每一层按照顶点数分块,

对于点>block的层称为重层,点<block的层称为轻层。

题解的这个分块方法主要不是解决空间的问题,而是通过block来解决时间的问题

对于轻层,因为点的个数<block,所以我们抓住这个,更新的时候用暴力更新,每一个点

对应树状数组更新一遍,时间O(block*logn),然后查询的时候,就用树状数组查询O(logn)

对于重层,重层的层的数量是n/block,我们抓住这个,更新的时候,就将更新的值保存在数组里O(1),

查询的时候再遍历每一层查询O(n/block*logn)

 

最后就是一些细节了,对于树状数组插入的时候,我们是按照每一个点的dfs序下标插入的,这样

我们查询x的子树时,我们只要知道x的dfs序和x的子树大小,就可以在树状数组中查到该子树内轻层

的点的权值和

然后对于重层,更新的时候只要存每一个重层更新的权值和,然后查询x的子树的时候,从x层遍历

在它下面的重层,对于每一个重层,依照dfs序进行两次二分,查出该层属于x的子数的点的个数inc

再*mark,就是答案了

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+10;

vector<int> edge[MAXN];
vector<int> dep[MAXN];
map<int,int> mdfs;
int sz[MAXN];
int md[MAXN];
ll c[MAXN];
ll mark[MAXN];
int n;
int maxdep,big[MAXN],cnt;

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int y)
{
	int i;
	for(i=x;i<=n;i+=lowbit(i))
		c[i]+=y;
}

ll sum(int x)
{
	int i;
	ll s=0;
	for(i=x;i>0;i-=lowbit(i))
	{
		s+=c[i];
	}
	return s;
}

int dfs(int x,int d,int &ind)
{
    maxdep=max(maxdep,d);
    mdfs[x]=ind;
    dep[d].push_back(ind);
    md[x]=d;
    sz[x]=1;
    for(int i=0;i<edge[x].size();i++)
    {
        ind++;
        sz[x]+=dfs(edge[x][i],d+1,ind);
    }
    return sz[x];
}



int main()
{
    int q;

    scanf("%d%d",&n,&q);
    int block=sqrt(n);
    int x,y;
    maxdep=0;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
    }

    int ind=1;
    dfs(1,1,ind);
    cnt=0;
    for(int i=1;i<=maxdep;i++)
    {
        if(dep[i].size()>block) big[cnt++]=i;
    }

    int id;
    for(int i=0;i<q;i++)
    {
        scanf("%d",&y);
        if(y==1)
        {
            scanf("%d%d",&x,&id);
			x++;
            if(dep[x].size()<=block)
            {
                for(int j=0;j<dep[x].size();j++)
                    add(dep[x][j],id);
            }
            else
            {
                mark[x]+=id;
            }
        }
        else
        {
            scanf("%d",&x);
            int nex=mdfs[x]+sz[x];
            ll res=sum(nex-1)-sum(mdfs[x]-1);
            int beg=lower_bound(big,big+cnt,md[x])-big;
            for(int j=beg;j<cnt;j++)
            {
				int h=big[j];
                int inc=upper_bound(dep[h].begin(),dep[h].end(),nex-1)-(lower_bound(dep[h].begin(),dep[h].end(),mdfs[x]));
                res+=(inc)*mark[h];
            }
            printf("%lld\n",res);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值