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;
}