数位dp
AcWing 339. 圆形数字
定义圆形数字如下:
把一个十进制数转换为一个无符号二进制数,若该二进制数中 0 的个数大于或等于 1 的个数,则它就是一个圆
形数字。现在给定两个正整数 a 和 b,请问在区间 [a,b] 内有多少个圆形数字。
输入格式
输入占一行,包含两个整数 a 和 b。
输出格式
输出一个整数,表示圆形数字的个数。
数据范围
- 1 ≤ a < b ≤ 2 × 10^9
输入样例:
2 12输出样例:
6代码:
cpp
// 数位 dp
#include <bits/stdc++.h>
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
int dp[50][1 << 10];
// val: 已经构造的数位, 模 k = val, 最总结果 == 0, 表示可以整除 k
// diff: 表示 0 与 1 的差, diff >= 0 表示 0 的个数大于等于 1 的个数
// 返回从 i 开始填数字
// is_limit 表示前面填的数字是否都是 n 对应位上, 如果为 true, 那么当前位至多为 int(s[i]), 否则至多为 9
// is_num 表示前面是否填了数字(是否跳过), 如果为 true, 那么当前位可以从 0 开始, 如果为 false, 那么我们可以跳过, 或者从 1 开始填数字
int f(int i, int diff, bool is_limit, bool is_num, string s) {
if (i == s.size())
return is_num && diff >= 50; // 找到了一个合法数字
if (!is_limit && is_num && dp[i][diff] != -1)
return dp[i][diff];
int res = 0;
if (!is_num) // 可以跳过当前数位
res = f(i + 1, diff, false, false, s);
int down = 1 - is_num;
int up = is_limit ? s[i] - '0' : 1; // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i] (否则就超过 n 啦)
for (int d = down; d <= up ; d ++ )
res += f(i + 1, diff + (d == 0 ? 1 : -1), is_limit && d == up, true, s);
if (!is_limit && is_num)
dp[i][diff] = res; // 记忆化
return res;
}
int calc(int x) {
int num[50];
int len = 0;
while (x) num[ ++ len] = x % 2, x /= 2; // 将数字转化为 r 进制
string s = "";
for (int i = len; i >= 1 ; i -- )
s += num[i] + '0'; // 变为字符串, 正序
int n = s.length();
memset(dp, -1, sizeof dp);
return f(0, 50, true, false, s);
}
void solve() {
int l, r;
cin >> l >> r;
cout << calc(r) - calc(l - 1) << endl;
}
int main() {
ios;
int T = 1;
while (T -- ) solve();
return 0;
}cpp
// 数位 dp
#include <bits/stdc++.h>
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
int dp[50][50][1 << 10];
// val: 已经构造的数位, 模 k = val, 最总结果 == 0, 表示可以整除 k
// cnt0: 0 的个数
// cnt1: 1 的个数
// 返回从 i 开始填数字
// is_limit 表示前面填的数字是否都是 n 对应位上, 如果为 true, 那么当前位至多为 int(s[i]), 否则至多为 9
// is_num 表示前面是否填了数字(是否跳过), 如果为 true, 那么当前位可以从 0 开始, 如果为 false, 那么我们可以跳过, 或者从 1 开始填数字
int f(int i, int cnt0, int cnt1, bool is_limit, bool is_num, string s) {
if (i == s.size())
return is_num && cnt0 >= cnt1; // 找到了一个合法数字
if (!is_limit && is_num && dp[i][cnt0][cnt1] != -1)
return dp[i][cnt0][cnt1];
int res = 0;
if (!is_num) // 可以跳过当前数位
res = f(i + 1, cnt0, cnt1, false, false, s);
int down = 1 - is_num;
int up = is_limit ? s[i] - '0' : 1; // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i] (否则就超过 n 啦)
for (int d = down; d <= up ; d ++ )
res += f(i + 1, cnt0 + (!d), cnt1 + (d == 1), is_limit && d == up, true, s);
if (!is_limit && is_num)
dp[i][cnt0][cnt1] = res; // 记忆化
return res;
}
int calc(int x) {
int num[50];
int len = 0;
while (x) num[ ++ len] = x % 2, x /= 2; // 将数字转化为 r 进制
string s = "";
for (int i = len; i >= 1 ; i -- )
s += num[i] + '0'; // 变为字符串, 正序
int n = s.length();
memset(dp, -1, sizeof dp);
return f(0, 0, 0, true, false, s);
}
void solve() {
int l, r;
cin >> l >> r;
cout << calc(r) - calc(l - 1) << endl;
}
int main() {
ios;
int T = 1;
while (T -- ) solve();
return 0;
}