第三届全国大学生算法设计与编程挑战赛 二进制-线段树

B. 二进制-线段树

二进制

题目链接:2021-2022年度第三届全国大学生算法设计与编程挑战赛-B题

描述

你是一个算法爱好者,在努力学习计算机知识。你知道,计算机最优美的地方在于二进制,这一点你在状压dp里面深有体会,当然二进制用在xor,and,or时也非常巧妙,更不用说nim游戏都能跟xor扯上关系了,而今天你又遇到了一道二进制的题目,对于爱思考的你,决定一直要把这道二进制题给切掉。

你遇到了很多十进制的数,对于这些数,它们都管理着它们对应的分层,比如数字6,它的二进制是110,则它管理着4(100),2(10),两个分段,当数字6在 [ 2 , 4 ] [2,4] [2,4]区间上增加3时,则代表二进制(100)和(10)两层在 [ 2 , 4 ] [2,4] [2,4]区间增加3。

本道题有两种操作,读入一个 o p t opt opt

o p t opt opt为1时 :读入一个数 a i a_i ai,一个区间 l i , r i l_i,r_i li,ri和一个增加的值 k i k_i ki,表示在这个数管理的分层中的每一个区间增加 k i k_i ki值;
o p t opt opt为2时:读入一个数 a i a_i ai,一个区间 l i , r i l_i,r_i li,ri,表示询问这个数管理的分层中每一个区间的值的总和。

输入

第一行两个整数 n , q n,q n,q,表示 n n n表示区间长度( 1 ≤ l i ≤ r i ≤ n ≤ 100000 1 \leq l_i \leq r_i \leq n \leq 100000 1lirin100000), q q q表示有 q q q( 1 ≤ q ≤ 100000 1 \leq q \leq 100000 1q100000)次操作。

数据保证( 1 ≤ a i ≤ 1024 1\leq a_i \leq1024 1ai1024), k i k_i ki i n t int int范围内

后面 q q q行,每行首先一个数 o p t opt opt表示进行的操作

o p t = 1 opt=1 opt=1时后接整数 a i a_i ai表示管理分层, l i , r i l_i,r_i li,ri表示区间, k i k_i ki表示增加的值

o p t = 2 opt=2 opt=2时后接整数 a i a_i ai表示管理分层, l i , r i l_i,r_i li,ri表示区间

输出

对于每个第二个操作,输出一行表示询问的答案。

输入样例

5 3
1 3 1 5 2
2 1 1 2
2 3 1 2

输出样例

4
8

思路

比赛的时候,糊出了一个空间复杂度 1024 ∗ 1 e 5 ∗ 2 1024*1e5*2 10241e52的线段树,以为是每个 a [ i ] a[i] a[i]都管理着不同的层,然后就一直不敢写,因为在分析复杂度的时候就已经不可行了。

但是最后仔细看题发现,是每个数虽然管理了很多分层,对于这些分层,大家都可以对其进行管理,这时候其实只需要考虑每个数管理着哪些分层,再每次操作都操作它管理的对应分层即可。

对于每个 a [ i ] a[i] a[i]所管理的分层,我们考虑 a [ i ] a[i] a[i]转换成二进制数之后1所在的位置,将其一一对应到一个分层,例如1管理着下标为1的分层,2管理着下标为2的分层,3管理着下标位1和2的分层,5管理着下标为1和3的分层。

由于1024对应的二进制位在第11位,因此线段树的层数开到12即可,具体的操作的时候就一个比较裸的线段树写法,套个板子就行。

找出二进制数的位置

上述寻找二进制数中1的位置的具体实现

vector<int> vv;
int k = 1;
while(a){
     
    if(a & 1)   //当前数为基数,则最低位一定是1
        vv.push_back(k);  //当前这个1的位置编号为k
    a >>= 1;k++;
}

AC代码

#include
#include
#include
#include
using namespace std;
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(0),cout.tie(0)
typedef long long ll;
const ll INF = 0x7fffffff;
const int maxn = 100005;
ll q, m;
struct node{
     
    ll tree[maxn*4];
    ll lazy[maxn*4];
}num[14];
void push_down(int p, int n, int l, int r){
     
    int mid = (l+r) >> 1;
    num[n].tree[p*2] += (mid - l + 1) * num[n].lazy[p];
    num[n].tree[p*2+1] += (r - mid) * num[n].lazy[p];

    num[n].lazy[p*2] += num[n].lazy[p];
    num[n].lazy[p*2+1] += num[n].lazy[p];
    num[n].lazy[p] = 0;
}
void update(int n, int p, int l, int r, int tl, int tr,ll v){
     
    if(tl <= l && r <= tr){
     
        num[n].lazy[p] += v;
        num[n].tree[p] += (r - l + 1) * v;
        return ;
    }
    if(num[n].lazy[p]){
     
        push_down(p, n, l, r);
    }
    int mid = (l+r) >> 1;
    if(tl <= mid) update(n, p*2, l, mid, tl, tr, v);
    if(tr > mid) update(n, p*2+1, mid+1, r, tl, tr, v);
    num[n].tree[p] = num[n].tree[p*2] + num[n].tree[p*2+1];
}
ll query(int n, ll p, ll l, ll r, ll tl, ll tr){
     
    if (tl <= l && r <= tr){
     
        return num[n].tree[p];
    }
    if(num[n].lazy[p])
        push_down(p, n, l, r);
    ll mid = (l+r) >> 1;
    ll sum = 0;
    if(tl <= mid)
        sum += query(n, p*2, l, mid, tl, tr);
    if(tr > mid)
        sum += query(n, p*2+1, mid+1, r, tl, tr);
    return sum;
}
int main() {
     
    ios;
    tie;
    scanf("%lld %lld", &m, &q);
    while(q--){
     
        int opt;
        int a, l, r;
        scanf("%d %d %d %d", &opt, &a, &l, &r);
        vector<int> vv;
        int k = 1;
        while(a){
     
            if(a & 1)
                vv.push_back(k);
            a >>= 1;k++;
        }
        int len = vv.size();
        if(opt == 1){
     
            int v; scanf("%d", &v);
            for(int i = 0; i < len; i++){
     
                update(vv[i], 1, 1, m, l, r, v);  //更新它管理的所有层
            }
        }
        if(opt == 2){
     
            ll ans = 0;
            for(int i = 0; i < len; i++){
     
                ans += query(vv[i], 1, 1, m, l, r);
            }
            printf("%lld\n", ans);
        }
    }
    return 0;
}

你可能感兴趣的:(线段树,ACM,1024程序员节,数据结构,算法)