洛谷 U18791 约幂数(线段树+二进制分解)

题面

无法传送门

题目大意就是给出一个区间[L,R],对于区间里的每个x,如果[1,n]中有一个y,使得y=(x << <script type="math/tex" id="MathJax-Element-28"><<</script>k)+c,其中c比(1 << <script type="math/tex" id="MathJax-Element-29"><<</script>k)小,那么a[y]+=d。给出a序列和每次修改的区间[L,R],每次问你a中某个数的值。


题解

作为某场GDKOI膜泥赛的T1,这道题是很水的。然而一开始我想了一些乱七八糟的东西。

对于修改直接考虑给区间[L,R]加权d,由于每次修改操作权可以叠加,所以搞个线段树。询问考虑一个数X能成为哪些数的约幂数。根据我玄学乱画,发现一个数X成为Y的约幂数,当且仅当Y是X二进制分解的前缀。于是每次logn分解X,在线段树上统计X被加了多少就行了。

下面简要证明一下Y是X的二进制前缀:

充分性:Y左移了k位,剩下的c一定在后面的0里出现,所以肯定小于最后那个1的权。

必要性:假如Y不是X的前缀,那么若X-Y得到的数所有的1都在X的最后一个1的右边,那么加起来必然不等于X,那么c必然有1出现在Y的最后一个1左边(或覆盖最后一个1),于是不满足条件。

于是本题愉快解决。

ps:听说也有直接枚举k的做法,不过下标会爆int什么的。。。


代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define maxn 100010

using namespace std;

typedef long long LL;

int n, m;
LL A[maxn];

struct Tnode{
    LL sum, add;
}Tree[maxn<<2];

void Down(int root, int mid, int Lson, int Rson, int L, int R){
    if(!Tree[root].add)  return;
    Tree[Lson].sum += Tree[root].add * (mid - L + 1);
    Tree[Lson].add += Tree[root].add;
    Tree[Rson].sum += Tree[root].add * (R - mid);
    Tree[Rson].add += Tree[root].add;
    Tree[root].add = 0LL;
}

void Update(int root, int L, int R, int x, int y, LL v){
    if(x > R || y < L)  return;
    if(x <= L && y >= R){
        Tree[root].sum += v * (R - L + 1);
        Tree[root].add += v;
        return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    Down(root, mid, Lson, Rson, L, R);

    Update(Lson, L, mid, x, y, v);
    Update(Rson, mid+1, R, x, y, v);

    Tree[root].sum = Tree[Lson].sum + Tree[Rson].sum;
}

LL Query(int root, int L, int R, int x){
    if(x > R || x < L)  return 0LL;
    if(L == x && x == R)  return Tree[root].sum;

    int mid = (L + R) >>1, Lson = root << 1, Rson = root << 1 | 1;

    Down(root, mid, Lson, Rson, L, R);

    LL temp1 = Query(Lson, L, mid, x);
    LL temp2 = Query(Rson, mid+1, R, x);

    return temp1 + temp2;
}

int main(){

    freopen("A.in", "r", stdin);
    freopen("A.out", "w", stdout);

    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++)  scanf("%lld", &A[i]);

    int op, l, r, x;
    LL d;

    for(int i = 1; i <= m; i++){
        scanf("%d", &op);
        if(op == 1){
            scanf("%d%d%lld", &l, &r, &d);
            Update(1, 1, n, l, r, d);
        }
        else{
            scanf("%d", &x);
            int temp = x;
            LL res = 0LL;
            while(temp){
                res += Query(1, 1, n, temp);
                temp >>= 1;
            }
            printf("%lld\n", A[x] + res);
        }
    }

    return 0;
}

这里写图片描述

小舟从此逝 江海寄余生

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值