题目链接:点击打开链接

题意:

给定一个区间,求区间内有多少个合法数(当这个数的二进制中0的个数>=1的个数称为合法数 二进制无前导0)

思路:

cnt[i]表示二进制长度为i位(即最高位为1,其他位任意)时的合法数个数。

sum[i] 就是二进制长度<=i位的合法数个数。

然后从最高位枚举到低位即可。维护当前0的个数。




#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iostream>
template <class T>
inline bool rd(T &ret) {
	char c; int sgn;
	if (c = getchar(), c == EOF) return 0;
	while (c != '-' && (c<'0' || c>'9')) c = getchar();
	sgn = (c == '-') ? -1 : 1;
	ret = (c == '-') ? 0 : (c - '0');
	while (c = getchar(), c >= '0'&&c <= '9') ret = ret * 10 + (c - '0');
	ret *= sgn;
	return 1;
}
template <class T>
inline void pt(T x) {
	if (x <0) {
		putchar('-');
		x = -x;
	}
	if (x>9) pt(x / 10);
	putchar(x % 10 + '0');
}
using namespace std;
typedef long long ll;
const int N = 100;
int bit[N];
ll cnt[N], C[N][N], sum[N];
ll dfs(int len){
	ll ans = sum[len - 1];//最高位(即len位一定是1)二进制长度<=len-1的合法数个数

	int zero = bit[len]==0;
	for (int i(len-1); i; i--)
	{
		if (bit[i] == 0){
			zero++;
			continue;
		}
		int tmp = zero + 1;//此时第i位可以填0也可以填1,若填0,则后面siz位可以任意填,填出来的数都不会大于x
		int siz = i - 1;
		if (tmp >= len - tmp)ans += 1LL << siz;
		else {
			for (int j = 0; j <= siz; j++)//同样枚举0的个数
			if (tmp + j >= len - tmp - j)
				ans += C[siz][j];
		}
	}
	ans += zero >= len - zero;//x这个数是不是合法
	return ans;
}
ll solve(ll x){
	if (x <= 1)return 1;
	int len = 0;
	for (ll tmp = x; tmp; tmp >>= 1)bit[++len] = tmp & 1;
	return dfs(len);
}
int main() {
	memset(C, 0, sizeof C);//计算组合数
	C[1][0] = C[1][1] = 1;
	for (int i = 2; i < N; i++){
		C[i][0] = 1;
		for (int j = 1; j < N; j++)
			C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
	}
	cnt[1] = 1; sum[1] = 1;
	for (int i = 2; i < N; i++){
		cnt[i] = 0;//二进制长度为i,最高位是1,其他位任意时的合法数个数
		for (int j = 0; j <= i - 1; j++) //因为最高位是1,所以自由位数有i-1个,枚举其中0的个数j,若0的个数j>=1的个数i-j,则cnt[i]的方法数+=在i-1个位置中选j个0
		if (j >= i - j)
			cnt[i] += C[i - 1][j];
		sum[i] = cnt[i] + sum[i - 1];
	}

	long long l, r;
	while (cin >> l >> r)
		cout << solve(r) - solve(l - 1) << endl;
	return 0;
}
/*
1 12
1 10
1 20


*/