POJ 3252 Round Numbers 数位dp(入门

时间:2022-12-16 11:49:56

题目链接:点击打开链接

题意:

给定一个区间,求区间内有多少个合法数(当这个数的二进制中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


*/