赛氪2021-2022冬季个人赛 -L.看错题【线段树】

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值