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 1≤li≤ri≤n≤100000), q q q表示有 q q q( 1 ≤ q ≤ 100000 1 \leq q \leq 100000 1≤q≤100000)次操作。
数据保证( 1 ≤ a i ≤ 1024 1\leq a_i \leq1024 1≤ai≤1024), 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 1024∗1e5∗2的线段树,以为是每个 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<iostream>
#include<string>
#include<stdio.h>
#include<vector>
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;
}