【题解】康娜的线段树

题目

小林是个程序媛,不可避免地康娜对这种人类的“魔法”产生了浓厚的兴趣,于是小林开始教她OI。

今天康娜学习了一种叫做线段树的神奇魔法,这种魔法可以维护一段区间的信息,是非常厉害的东西。康娜试着写了一棵维护区间和的线段树。由于她不会打标记,因此所有的区间加操作她都是暴力修改的。具体的代码如下:

struct Segment_Tree{
     
#define lson (o<<1)
#define rson (o<<1|1)
    int sumv[N<<2],minv[N<<2];
    inline void pushup(int o){
     sumv[o]=sumv[lson]+sumv[rson];}
    inline void build(int o,int l,int r){
     
        if(l==r){
     sumv[o]=a[l];return;}
        int mid=(l+r)>>1;
        build(lson,l,mid);build(rson,mid+1,r);
        pushup(o);
    }
    inline void change(int o,int l,int r,int q,int v){
     
        if(l==r){
     sumv[o]+=v;return;}
        int mid=(l+r)>>1;
        if(q<=mid)change(lson,l,mid,q,v);
        else change(rson,mid+1,r,q,v);
        pushup(o);
    }
}T; 

在修改时,她会这么写:

for(int i=l;i<=r;i++)T.change(1,1,n,i,addv);

显然,这棵线段树每个节点有一个值,为该节点管辖区间的区间和。

康娜是个爱思考的孩子,于是她突然想到了一个问题:

如果每次在线段树区间加操作做完后,从根节点开始等概率的选择一个子节点进入,直到进入叶子结点为止,将一路经过的节点权值累加,最后能得到的期望值是多少?

1 ≤ n , m ≤ 1 0 6 1 \leq n,m \leq 10^6 1n,m106

− 1000 ≤ a i , x ≤ 1000 -1000 \leq a_i,x \leq 1000 1000ai,x1000

题解

思路

对于本题,我们有一个最朴素的想法就是对每一层求一个和,乘上到达这一层的概率,即 1 2 d e p − 1 \frac{1}{2^{dep-1}} 2dep11,但这样是无法比较快速的应对修改操作的

基于这种思路,我们换个思考的方向,即思考一个 i i i

显然包含 i i i 的块是连续的,如下图:

【题解】康娜的线段树_第1张图片

对于包含 2 2 2 下标的块,一定是连续的

【题解】康娜的线段树_第2张图片

而他所做的贡献就是上图标注的

所以现在我们只需要知道每一个下标 i , i ∈ [ 1 , n ] i,i\in [1,n] i,i[1,n],它对应的叶节点的深度是多少

所以为了实现这一目标,我们来单独去求这个东西:

求深度是可以用一个搜索来解决的,但是我们要快,一个搜索是不够的

那么优化搜索的方法是什么?

记忆化!

对的,我们在这里有一个比较好玩的性质,对于每一个长度相同的区间,这两个区间中相对应的两个下标 i , j i,j i,j 到这两个下标的最深深度距离是一样的

可能有点抽象,我们举个栗子:

上图中第 2 2 2 层的 [ 1 , 3 ] [1,3] [1,3] [ 4 , 6 ] [4,6] [4,6],区间长度一样

那么 [ 1 , 3 ] [1,3] [1,3]区间中的第 2 2 2 个数也就是 2 2 2,它到 [ 2 , 2 ] [2,2] [2,2]的距离是 2 2 2

[ 4 , 6 ] [4,6] [4,6]区间中的第 2 2 2 个数也就是 5 5 5,它到 [ 5 , 5 ] [5,5] [5,5]的距离也是 2 2 2

当然,我们多找几个来举例,也是一样的

也就是说我们是可以通过这种方法实现记忆化

void dfs(int l,int r,int sum){
     
	if(mem[r-l+1]==true){
     
		for(int i=l;i<=r;i++){
     
			s[i]=s[i-l+s1[r-l+1]]-s2[r-l+1]+sum;
		}
		return;
	}
	if(l==r){
     
		s[l]=sum;
		w=max(w,sum);
		return;
	}
	int mid=(l+r)>>1;
	dfs(l,mid,sum+1);
	dfs(mid+1,r,sum+1);
	s1[r-l+1]=l;
	s2[r-l+1]=sum;
	mem[r-l+1]=true;
}

处理好了深度,剩下的事情就真的简单了,一个前缀和, o v e r over over

code

#include
#include
using namespace std;
const int MAXN=1000005;
int s1[MAXN];
int s2[MAXN];
long long s[MAXN];
bool mem[MAXN];
long long pre[MAXN];
int dep[MAXN],pre_d[MAXN];
int w;
void dfs(int l,int r,int sum){
     
	if(mem[r-l+1]==true){
     
		for(int i=l;i<=r;i++){
     
			s[i]=s[i-l+s1[r-l+1]]-s2[r-l+1]+sum;
		}
		return;
	}
	if(l==r){
     
		s[l]=sum;
		w=max(w,sum);
		return;
	}
	int mid=(l+r)>>1;
	dfs(l,mid,sum+1);
	dfs(mid+1,r,sum+1);
	s1[r-l+1]=l;
	s2[r-l+1]=sum;
	mem[r-l+1]=true;
}
int main(){
     
	int n,m;
	long long qwq;
	scanf("%d %d %lld",&n,&m,&qwq);
	qwq*=128;
	dfs(1,n,1);
	dep[1]=qwq;
	pre_d[1]=qwq;
	for(int i=2;i<=w;i++){
     
		dep[i]=dep[i-1]>>1;
		pre_d[i]=pre_d[i-1]+dep[i];
	}
	for(int i=1;i<=n;i++){
     
		pre[i]=pre[i-1]+pre_d[s[i]];
	}
//	for(int i=1;i<=n;i++){
     
//		cout<
//	}
	long long ans=0;
	for(int i=1;i<=n;i++){
     
		int x;
		scanf("%d",&x);
		ans+=(pre[i]-pre[i-1])*x;
	}
	for(int i=1;i<=m;i++){
     
		int l,r,x;
		scanf("%d %d %d",&l,&r,&x);
		ans+=(pre[r]-pre[l-1])*x;
		printf("%lld\n",ans/128);
	}
return 0;
}

你可能感兴趣的:(【题解】康娜的线段树)