Date:2022.03.27
题目背景
小Y同学发现自己的数学思维真是太差了,自己写题目的时候总喜欢去写数据结构题,因为数据结构大都是模板,就是背了板子就能强行干题,但是数据结构只要有一点思维在里面,他就不会做了,因此他决定去做题网站写一写题。
巧合的是他看到了一道题,这道题要求的是,给定一颗线段树,每次区间加一个xx,如果每次在线段树区间加操作做完后,从根节点开始等概率的选择一个子节点进入,直到进入叶子结点为止,将一路经过的节点权值累加,最后能得到的期望值是多少?
不巧的是,小Y同学看错题辣,他把进入叶子节点看成了,依次选一个叶子节点进入,然后一直走到根节点,问最后得到的期望值是多少,巧合的是,他这么写了以后还把样例都过了,然后喜提WA声一片。
小Y同学很难受,于是把他看错的题意给你,看看你是否能做出这道题。
题目描述
首先题目描述里面,对于线段树的下放过程是这样的
void build(int rt,int l,int r
{
if(l==r){tr[rt]=a[l];return ;}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
tr[rt]=tr[ls]+tr[rs];
}
void update(int rt,int l,int r,int q,int v){
if(l==r){tr[rt]+=v;return;}
int mid=(l+r)>>1;
if(q<=mid)update(ls,l,mid,q,v);
else update(rs,mid+1,r,q,v);
}
//主函数中建树和下放的代码为
build(1,1,n);
for(int i=l;i<=r;i++)
update(1,1,n,i,v);
小Y同学为了让题目更简单,对于n个整数,n是一个2^x 的整数,来保证这个线段树是一棵满二叉树。
他现在想问你,每次给一段区间加上一个整数(每次操作对后续有影响),然后依次在线段树中选一个叶子节点,一直走到根节点,将一路经过的节点权值累加,问把每一个叶子节点选择一遍后的全部的总和是多少。
Input
第一行整数 n,m, 表示线段树维护的原序列的长度,询问次数。
第二行 n(1≤n≤2 17) 个数,表示原序列。
接下来 m(1≤m≤100000) 行,每行三个数 l,r,x 表示对区间[l,r]加上 x。
Output
共 m 行,表示查询的权值和。
Sample Input 1
8 2
1 2 3 4 5 6 7 8
1 3 4
1 8 2
Sample Output 1
720
960
思路:题意一点用没有,就是求每次区间修改一些数,之后再求由每个叶结点到根节点一路上权值和。我们发现从下往上加和时,每层的所有结点用的次数是1、2、4、8…按公比为2增长,因此每次加和的问题解决了。
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
typedef long long LL;
LL n,m,w[N];
struct node
{
int l,r;
LL sum,add;
}tr[N*4];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)//需要分裂一个结点的区间 与 用到sum的都要pushdown
{
auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];//简化结点
if(root.add)//当前结点有懒标记
{
left.add+=root.add;
left.sum+=(LL)(left.r-left.l+1)*root.add;
right.add+=root.add;
right.sum+=(LL)(right.r-right.l+1)*root.add;
root.add=0;//操作结束勿忘清空
}
}
void build(int u,int l,int r)
{
if(l==r)
tr[u]={l,r,w[r],0};
else
{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r,int d)
{
if(tr[u].l>=l && tr[u].r<=r)//修改大区间中包含整块的,直接修改该块根节点的懒标记,并修改该结点的区间和sum
{
tr[u].sum+=(LL)(tr[u].r-tr[u].l+1)*d;
tr[u].add+=d;
}
else//修改大区间中不完全包含整块的,必须pushdown分裂【向下传递该点的懒标记,并递归修改左右子树】
{
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,d);
if(r>mid) modify(u<<1|1,l,r,d);
pushup(u);//递归修改后该根节点的子节点区间和必有变化,pushup传递到根节点更新根节点的区间和
}
}
LL query(int u,int l,int r)
{
if(tr[u].l>=l && tr[u].r<=r)//查询的大区间中有连续的部分直接返回其区间和
return tr[u].sum;
//必须分裂
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
LL sum=0;
if(l<=mid) sum+=query(u<<1,l,r);
if(r>mid) sum+=query(u<<1|1,l,r);
return sum;
}
LL qmi(LL a,LL k)
{
LL res=1;
while(k)
{
if(k&1) res=res*a;
a=a*a;
k>>=1;
}
return res;
}
LL lg(LL x)
{
LL dep=0;
if(x==1) return 0;
while(x)
{
x/=2;
dep++;
}
return dep;
}
int main()
{
cin>>n>>m;
LL x=n,dep=0;
while(x)
{
x/=2;
dep++;
}
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--)
{
LL x,y,z;cin>>x>>y>>z;
modify(1,x,y,z);
cout<<query(1,1,n)*(qmi(2,dep)-1)<<'\n';
}
return 0;
}