河南萌新联赛2024第(一)场 D小蓝的二进制询问

题目大意:

给定一个区间(l,r),求出在区间中所有整数在二进制下1的个数之和

思路:

前缀和 数据范围太大 全部放数组中实现不了
通过打表,可以发现一种以2^n 递增的规律,递归计算比二分还快2 ^64~=1e18

(如果询问次数过多,也可能 可以将有规律的部分离散化,预处理存入数组中,只考虑没有规律的那部分)

如果需要的数据不在一组完整的规律中
不完整的部分内也有很多 例如在(2^63-2 ^64)中
因此可以将这一部分不完整的数据处理为多个完整的小区间计算
例如剩下6()个数,可以把6分为2^2+2 ^1 (2 ^x+2 ^y)

递归处理完整部分规律

1
10 11
100 101 110 111
1000 1001 1010 1011 1100 1101 1110 1111
其中二进制所含1个数
1 …1
1 2 …3
1 2 2 3 …8
1 2 2 3 2 3 3 4 …20

可得出 每组数据 的个数为2^i
每组数据 需将 上一组数据 复制一次 每个复制的数据还需加上1(共2^i个1)
而第一组数据的前一组没有数据,因此将第一组数据不放入递归中,先把1加入sum
每组数据满足 t=t*2+2^i(t从第二组开始递归)
每次递归t时,从x中减掉已经加过的数据,直至一组所求数据个数小于2^(i+1)个(不满足一组完整的数据)

递归处理剩余部分规律

例如剩余14个数未计算,while()循环后传入add_1个数为14
此时可以将 该不完整部分 分为 完整的部分
例如将14(1110)分为2^ 3+2^ 2+2^ 1 (2^ x+2^ y+2^ z)
可以通过将14转换为二进制1110,位数上出现1即可分解为一个完整部分
由于分离出的数据存在误差
例如
1 2
1 2 2 3
1 2 2 3 2 3 3 4
1 2 2 3 2 3 3 4 2 3 3 4 3 (4) 4 5
剩余部分的14(1110)可分为对应的8(1000),4(100),2(10)

14(1110) 1 2 2 3 2 3 3 4 (2 3 3 4) (3 4)
8(1000) 1 2 2 3 2 3 3 4
4(100) 1 2 2 3
2(10) 1 2

由此可以看出 分离出的数据从第二组开始,每组 相较于 实际组 均产生误差
分离出的 第一组 与 实际第一组 相比 相差 0
分离出的 第二组 与 实际第二组 相比 相差 (1)* 2^i 个1
分离出的 第三组 与 实际第三组 相比 相差 (2)* 2^i 个1
需要每组求和时均要加上对应实际的t *2^i个1

Solved
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+10;
const int mod=998244353;

int add(int x);
int add_1(int x);

int add(int x)
{
	//剔除第一组数据(总个数-1)x--
	//将第一组数据加入和sum
	//从第二组开始递归 t值设为第一组数据
	int i=0,t=1,sum=1;
	x--;

	//一共需要求x个数据中包含的1
	//此时放入while循环中的x可表示为需要求的总个数
	while(x>0){
		//如果剩余个数不满足完整部分
		if(x< (1ll<<(i+1) ) ){//将剩余部分未求个数传入add_1
			sum+=add_1(x);
			break;//结束完整部分求和
		}

		//处理完整部分求和
		//递归出的每组数据都加入sum中
		t=t*2+(1ll<<i);//t=t*2+2^i
		sum+=t%mod;
		sum%=mod;

		//加完更新所需要加的个数
		x-=(1ll<<(i+1));//本组加的个数为2^(i+1)
		//将待增条件i更新为本组(每组数据中需要补的1 总个数2^i)
		i++;
	}
	//如果传入x=0,返回0
	if(x==-1) return 0;

	return sum%mod;
}
int add_1(int x)
{
	//将x存入bitset中转化为二进制,找到其中存在多少个2^i
	bitset<64> b(x);
	//sum初始为0 误差值从第一组开始 第一组t=0,每组依次递增一组2^i
	int t=0,sum=0;
	//从高位开始
	for(int i=63;i>=0;i--){
		//找高位1(第i组)
		if(b[i]==0) continue;
		//找到后进行处理

		//先将f第一组1加上,然后递归实现2^i个数的和
		int f=1;
		for(int j=0;j<i;j++){
			f=f*2+(1ll<<j);
		}

		//递归至该2^i个数时存入sum
		sum+=f%mod;

		//加上误差
		sum+=(1ll<<i)*t%mod;//(t-1)个2^i
		sum%=mod;
		t++;//更新每组误差
	}
	return sum%mod;
}

void solve()
{
	int l,r;cin>>l>>r;
	cout<<( add(r)+mod-add(l-1) )%mod<<endl;
}

signed main()
{
	std::ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值