zoukankan      html  css  js  c++  java
  • BZOJ 4765(分块+树状数组)

    题面

    传送门
    "奋战三星期,造台计算机"。小G响应号召,花了三小时造了台普通计算姬。普通计算姬比普通计算机要厉害一些
    。普通计算机能计算数列区间和,而普通计算姬能计算树中子树和。更具体地,小G的计算姬可以解决这么个问题
    :给定一棵n个节点的带权树,节点编号为1到n,以root为根,设sum[p]表示以点p为根的这棵子树中所有节点的权
    值和。计算姬支持下列两种操作:
    1 给定两个整数u,v,修改点u的权值为v。
    2 给定两个整数l,r,计算sum[l]+sum[l+1]+....+sum[r-1]+sum[r]
    尽管计算姬可以很快完成这个问题,可是小G并不知道它的答案是否正确,你能帮助他吗?

    分析

    刚看到这题想到树剖套线段树的做法,但是时间复杂度太大,无法接受。既然用log级的数据结构无法维护,很容易想到分块。按节点编号分块,每个块维护sum[l]+sum[l+1]+...+sum[r],[l,r]为块边界

    这道题的突破口是考虑每个节点的贡献

    对于每个节点u来说,它的值被改为v,只会影响u的祖先节点的子树和。

    因此可以分块预处理(f[i][j])表示第i个节点有多少个祖先节点在第j个块里面。dfs的时候维护一个数组cnt[i]存储当前搜索树中有多少个节点在第i块中,递归到某个节点x时cnt[x]++,回溯时cnt[x]--,这样(f[x][i]=cnt[i])

    那么如果某个节点x的值增加了d,我们就遍历每个块i,每个块i的和增加(f[x][i] imes d)

    查询的时候整块可以直接加上和。对于不完整的部分,直接查询每个节点的子树和再相加,用dfs序+树状数组的方法维护。树状数组的第i个位置存储第i个节点的值,查询子树和的时候利用dfs序可以(O(log n))的时间内查询出第i个节点的子树和

    坑点:

    此题不能用树状数组,会被卡常。记得开unsigned long long,否则会爆

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath> 
    #define maxn 100005
    #define maxb 505
    #define ll unsigned long long
    using namespace std;
    int n,m;
    struct BIT {
    	ll t[maxn];
    	inline ll lowbit(int x){
    		return x&(-x);
    	}
    	void update(int x,ll v){
    		while(x<=n){
    			t[x]+=v;
    			x+=lowbit(x);
    		}
    	}
    	ll sum(int x){
    		ll ans=0;
    		while(x>0){
    			ans+=t[x];
    			x-=lowbit(x);
    		}
    		return ans;
    	}
    	ll query(int l,int r){
    		return sum(r)-sum(l-1);
    	}
    
    }T;
    
    struct edge {
    	int from;
    	int to;
    	int next;
    } E[maxn<<1];
    int head[maxn];
    int esz=1;
    void add_edge(int u,int v) {
    	esz++;
    	E[esz].from=u;
    	E[esz].to=v;
    	E[esz].next=head[u];
    	head[u]=esz;
    }
    
    
    ll a[maxn];
    int bsz,bcnt;//块大小,块个数
    inline int lb(int id) {
    	return (id-1)*bsz+1;
    }
    inline int rb(int id) {
    	return (id*bsz>n)?n:id*bsz;
    }
    int cnt[maxb];//cnt[i]存第i个块内有多少个当前搜索树中的节点
    int f[maxn][maxb]; //f[i][j]存第i个块内有多少个j的祖先节点
    ll sum[maxn];//整块的和 
    int id[maxn];
    int dfnl[maxn],dfnr[maxn];
    int tim=0;
    void dfs(int x,int fa) {
    	dfnl[x]=++tim;
    	cnt[id[x]]++;
    	for(int i=1; i<=bcnt; i++) {
    		f[x][i]=cnt[i];
    	}
    	for(int i=head[x]; i; i=E[i].next) {
    		int y=E[i].to;
    		if(y!=fa) {
    			dfs(y,x);
    		}
    	}
    	dfnr[x]=tim;
    	cnt[id[x]]--;
    }
    
    void change(int u,ll v){
    	ll pre=T.query(dfnl[u],dfnl[u]);
    	T.update(dfnl[u],v-pre);
    	for(int i=1;i<=bcnt;i++){
    		sum[i]+=(ll)f[u][i]*(v-pre);
    	}
    } 
    ll ask(int l,int r){
    	ll ans=0;
    	for(int i=l;i<=min(r,rb(id[l]));i++){
    		ans+=T.query(dfnl[i],dfnr[i]);
    	}
    	for(int i=id[l]+1;i<id[r];i++){
    		ans+=sum[i]; 
    	}
    	if(id[l]!=id[r]){
    		for(int i=lb(id[r]);i<=r;i++){
    			ans+=T.query(dfnl[i],dfnr[i]);
    		}
    	}
    	return ans;
    }
    int main() {
    	int op,u,v,l,r,root;
    	ll k;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%llu",&a[i]);
    	for(int i=1;i<=n;i++){
    		scanf("%d %d",&u,&v);
    		if(u==0) root=v;
    		else{
    			add_edge(u,v);
    			add_edge(v,u);
    		}
    	}
    	bsz=sqrt(n);
    	bcnt=1;
    	for(int i=1;i<=n;i++){
    		id[i]=bcnt;
    		if(i%bsz==0) bcnt++;
    	}
    	dfs(root,0);
    	for(int i=1;i<=n;i++){
    		T.update(dfnl[i],a[i]);
    	}
    	for(int i=1;i<=n;i++){
    		sum[id[i]]+=T.query(dfnl[i],dfnr[i]);
    	}
    	for(int i=1;i<=m;i++){
    		scanf("%d",&op);
    		if(op==1){
    			scanf("%d",&u);
    			scanf("%llu",&k);
    			change(u,k);
    		}else{
    			scanf("%d %d",&l,&r);
    			printf("%llu
    ",ask(l,r));
    		}
    	}
    }
    
  • 相关阅读:
    微信公众号扫一扫接口
    JDBC-用户登录验证(sql注入)
    JDBC
    Shell脚本
    java-变量总结
    java-那些方法不能被重写
    java-数组工具类
    java-类初始化与实例初始化
    java-static
    java-native修饰符
  • 原文地址:https://www.cnblogs.com/birchtree/p/10533477.html
Copyright © 2011-2022 走看看