牛客练习赛4 C-Sum 线段树+二进制拆分

牛客练习赛4 C-Sum 线段树+二进制拆分


传送门: https://ac.nowcoder.com/acm/contest/16/C

题意

给 你 n 个 数 A 1 . . . A n , m 个 操 作 。 给你n个数A_1...A_n,m个操作。 nA1...Anm
操 作 分 两 种 : 操作分两种:

  • 操 作 一 : 1    x    y , 将 A x 改 成 y 操作一:1\;x\;y,将A_x改成y 1xyAxy
  • 操 作 二 : 2    l    r , 求 [ A l . . . A r ] 所 有 子 集 的 & 并 m o d    1 e 9 + 7 操作二:2\;l\;r,求[A_l...A_r]所有子集的\&并mod\;1e9+7 2lr[Al...Ar]&mod1e9+7

思路

这 题 一 看 就 知 道 树 状 数 组 或 者 线 段 树 去 做 。 这题一看就知道树状数组或者线段树去做。 线
因 为 和 位 运 算 有 关 , 所 以 考 虑 每 个 数 的 二 进 制 位 。 因为和位运算有关,所以考虑每个数的二进制位。

比 如 ( 3 ) 2 = 11 , 则 贡 献 为 2 0 ∗ ( 2 1 − 1 ) + 2 1 ∗ ( 2 1 − 1 ) 比如(3)_2=11,则贡献为2^0*(2^1-1)+2^1*(2^1-1) (3)2=1120(211)+21(211)

因 为 是 & 操 作 , 所 以 该 位 上 必 须 都 是 1 才 行 , 所 以 统 计 该 位 上 有 多 少 1 即 可 。 因为是\&操作,所以该位上必须都是1才行,所以统计该位上有多少1即可。 &11

比 如 1 , 2 , 3 为 1 , 10 , 11 , 则 组 合 起 来 为 22 , 贡 献 为 2 0 ∗ ( 2 2 − 1 ) + 2 1 ∗ ( 2 2 − 1 ) 比如1,2,3为1,10,11,则组合起来为22,贡献为2^0*(2^2-1)+2^1*(2^2-1) 1231,10,112220(221)+21(221)

如 何 更 新 和 查 询 ? 线 段 树 ! 如何更新和查询?线段树! 线

只 不 过 更 新 的 是 一 个 数 的 二 进 制 数 组 而 已 。 只不过更新的是一个数的二进制数组而已。

Code

#include "bits/stdc++.h"
using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;

const int N = 4e5 + 10;

#define lc u << 1
#define rc u << 1 | 1
#define mid (t[u].l + t[u].r) / 2

struct Tree {
    int l, r;
    ll OR[55];
}t[N << 2];
ll bit[N], a[N];

inline void push_up(int u) {
    for(int i = 0;i <= 32; i++) {
        t[u].OR[i] = (t[lc].OR[i] + t[rc].OR[i]) % mod;
    }
}

void build(int u, int l, int r) {
    t[u].l = l; t[u].r = r;
    if(l == r) {
        for(int i = 0;i <= 32; i++) {
            t[u].OR[i] += a[l] & 1;
            a[l] >>= 1;
        }
        return ;
    }
    int m = (l + r) >> 1;
    build(lc, l, m);
    build(rc, m + 1, r);
    push_up(u);
}

void modify(int u, int p, ll v) {
    if(t[u].l == t[u].r) {
        for(int i = 0;i <= 32; i++) {
            t[u].OR[i] = v & 1;
            v >>= 1;
        }
        return ;
    }
    if(p <= mid) modify(lc, p, v);
    else modify(rc, p, v);
    push_up(u);
}

vector<ll> query(int u, int ql, int qr) {
    if(ql <= t[u].l && t[u].r <= qr) {
        vector<ll> ans(33, 0);
        for(int i = 0;i <= 32; i++) ans[i] = t[u].OR[i];
        return ans;
    }
    vector<ll> ans(33, 0), temp1(33, 0), temp2(33, 0);
    if(ql <= mid) temp1 = query(lc, ql, qr);
    if(qr > mid)  temp2 = query(rc, ql, qr);
    for(int i = 0;i <= 32; i++) ans[i] = temp1[i] + temp2[i];
    return ans;
}

void solve() {
    bit[0] = 1;
    for(int i = 1;i < N; i++) bit[i] = bit[i - 1] * 2 % mod;

    int n; scanf("%d",&n);
    for(int i = 1;i <= n; i++) scanf("%lld",&a[i]);
    build(1, 1, n);
    int m; scanf("%d",&m);
    while(m--) {
        int opt; scanf("%d",&opt);
        if(opt == 1) {
            int x; ll y; scanf("%d%lld",&x,&y);
            modify(1, x, y);
        }
        else {
            int l, r; scanf("%d%d",&l,&r);
            vector<ll> ans = query(1, l, r);
            ll res = 0;
            for(int i = 0;i <= 32; i++) {
                res = (res + bit[i] % mod * (bit[ans[i]] - 1 + mod) % mod) % mod;
            }
            printf("%lld\n",res);
        }
    }
}

signed main() {
    solve();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值