[贪心][预处理+二分][好题]Find the Number 2022ICPC第一场网络选拔赛D

22 篇文章 0 订阅
3 篇文章 0 订阅

Let's call a number x good if x is an interger and ctz(x)=popcount(x).

ctz(x) is the number of trailing zeros in the binary representation of x.

popcount(x) is the number of 1's in the binary representation of x.

For example:

ctz(12)=ctz(11002​)=2 and popcount(12)=popcount(11002​)=2, so 12 is a good number.

Now you are given an interval [l,r], you need to find a good number in it. If there are multiple solutions, print any of them; if there is no solution, print −1 instead.

输入格式:

The first line contains a single integer T (1≤T≤105) --- the number of test cases.

For each test case, there is one single line containing two integers l,r (1≤l≤r≤109) --- the interval.

输出格式

For each test case, print one line containing the answer.

If there are multiple solutions, print any of them; if there is no solution, print −1 instead.

输入样例:

5
38 47
57 86
23 24
72 83
32 33

输出样例:

-1
68
-1
-1
-1

题意: 给出区间[l, r],求在该区间内有多少数字的二进制表示满足后缀0个数等于全部1个数。

分析: 由于满足题意的数字并不是特别多,大概是5e5左右个,当时想到了打表,但显然是没法把这些数字全部放进代码里的,所以当时这条路就没走下去,但赛后发现完全可以把这些数字暴力求出来,然后放入15个vector中,暴搜的复杂度很低,大概也是5e5级别的,当求出符合题意的全部数字后这道题就很简单了,对于每个询问直接找出第一个大于等于l的数字,然后看它是否小于等于r,不得不说这种思路确实很强。

赛时想的是另一种贪心的思路,首先枚举后缀零个数,假设后缀零有i个,然后考虑l和r的二进制表示,从第30位到第0位枚举。而答案应该出现在二者中间,如果设二者最早不同的位置为t的话,那么从第30位到第t+1位的位置x取值是唯一的,上下限都被l和r卡死了。而r的第t位肯定是1,l的第t位肯定是0,此时x可以选择取0或者是1。如果x取0那么最终值肯定小于r,但是并不一定大于等于l,所以此时贪心地想剩余的1的位置一定是连续地放在第t-1位及其后面的位置。如果x取1那么最终值一定大于l,但是同样不保证它小于等于r,所以贪心地想剩余的1一定放在i+1位及其往前的位置。最后连续放1的时候需要判断下空间是否够放,如果不够放那说明这样是构不成答案的。在一开始被l和r卡死的位置也要注意动态更新剩余1的个数,同时这时候如果剩余1个数减到0了也算是一个答案,也要记得判断。

具体代码如下:

预处理+二分

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#define int long long
using namespace std;

vector<int> num[16];//num[i]保存所有后缀0个数为i的符合题意的数字 

void dfs(int x, int now, int val, int id){
	if(30-now < x) return;
	if(x == 0){
		num[id].push_back(val);
		return;
	}
	for(int i = now+1; i <= 30; i++)
		dfs(x-1, i, val+(1<<i), id);
}

signed main()
{
	for(int i = 1; i <= 15; i++){//有i个后缀0 
		dfs(i-1, i, 1<<i, i);
		sort(num[i].begin(), num[i].end());
	}
	int T;
	cin >> T;
	while(T--){
		int l, r;
		scanf("%lld%lld", &l, &r);
		for(int i = 1; i <= 15; i++){
			if(lower_bound(num[i].begin(), num[i].end(), l) != num[i].end()){
				int x = *lower_bound(num[i].begin(), num[i].end(), l);
				if(x <= r){
					printf("%lld\n", x);
					break;
				}
			}
			if(i == 15) puts("-1");
		}
	}
	
    return 0;
}

 贪心

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#define int long long
using namespace std;

void solve(){
	int l, r;
	scanf("%lld%lld", &l, &r);
	for(int i = 1; i <= 16; i++){//枚举0的个数 
		int num = i-1;
		if(num == 0){
			if(l <= (1<<i)){
				printf("%lld\n", 1<<i);
				return;
			}
			else continue;
		}
		int ans = 1<<i;
		for(int j = 30; j >= i+1; j--){
			if(((l>>j)&1) == ((r>>j)&1)){
				if(((l>>j)&1) == 1){
					ans += 1<<j;
					num--;
					if(num == 0){
						if(ans >= l && ans <= r){
							printf("%lld\n", ans);
							return;
						}
						else break;
					}
				}
			}
			else{//肯定r取1,l取0 
				//此时第j位可以选0或1
				//选0时肯定小于r,不一定大于等于l,所以尽量靠左填1
				int cpy = ans;
				if(j-(i+1) >= num){//选0
					for(int k = j-1, cnt = 0; cnt < num; k--, cnt++)
						ans += 1<<k; 
					if(ans >= l){
						printf("%lld\n", ans);
						return;
					}
				} 
				ans = cpy;
				//选1时肯定大于l,不一定小于等于r,所以尽量靠右填1
				if(j-(i+1) >= num-1){//选1
					ans += 1<<j;
					for(int k = i+1, cnt = 0; cnt < num-1; k++, cnt++)
						ans += 1<<k; 
					if(ans <= r){
						printf("%lld\n", ans);
						return;
					}
				}
				break; 
			}
		}
	}
	puts("-1"); 
}

signed main()
{
	int T;
	cin >> T;
	while(T--) solve(); 
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值