[USACO06NOV] Round Numbers S
题目链接:https://www.luogu.com.cn/problem/P6218
思路:
很明显的数位\(DP\)……
因为二进制数中 \(0/1\) 的个数有关,可以确定 \(DP\) 状态为 \(f[i][j][k]\),分别为数位、 \(0\) 的个数、\(1\)的个数。继而考虑状态转移……
讲解都在代码里了,因为面向萌新,内容较为仔细冗杂,大佬勿喷\(QAQ\)。
完整代码:
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 40;
ll a,b,f[N][N][N];//f[i][j][k]表示共有i位,其中含有j个0和k个1的数字个数
int tot,dig[N];
ll dfs(int len,int sum0,int sum1,bool f0,bool f1) {
/*
len : 数位
sum0 : 0的个数
sum1 : 1的个数
f0 : 是否能取到最大值(1不能/0能)
f1 : 是否在前导零里面(1是/0不是)
*/
if(!len) return (f1 || sum0 >= sum1);//数位为零且满足条件,返回值为1
if(!f1 && !f0 && f[len][sum0][sum1]) return f[len][sum0][sum1];//记忆化
ll res = 0;
for(int i = 0; i <= (f0 ? dig[len] : 1); i ++) {
if(f1 && !i) res += dfs(len - 1,0,0,i == dig[len] && f0,1);//如果位是前导零,1/0个数仍是0,继续向前找
/*
此处 i == dig[len] && f0 的意思是,
除了当前位为给定数上该位的值(不能更大)而且不能取到最大值的情况,
其他情况都能取到最大值(1不能取最大值/0能)
*/
else {//不是前导零
if(!i) res += dfs(len - 1,sum0 + 1,sum1,f0 && i == dig[len],f1);
//如果当前位为 0,(数位减1(当前数位确定),0的个数加1,1不变,同上,当前f1状态未改变)
else res += dfs(len - 1,sum0,sum1 + 1,f0,0);//解释同上,转换一下
}
}
if(!f0 && !f1) f[len][sum0][sum1] = res;//记忆化
return res;
}
ll solve(ll x) {
tot = 0;
while(x) {//分解数位
dig[++tot] = x & 1;
x >>= 1;
}
return dfs(tot,0,0,1,1);
//初始状态,0/1个数均为0,当前位不能取到最大值(有限制),当前位仍是前导零(未赋值)
}
int main() {
scanf("%lld %lld",&a,&b);
printf("%lld",solve(b) - solve(a - 1));
return 0;
}