对于位数比较多的数,这样的过程中有许多重复的部分。例如,从 7000 数到 7999、从 8000 数到 8999、和从 9000 数到 9999 的过程非常相似,它们都是后三位从 000 变到 999,不一样的地方只有千位这一位,所以我们可以把这些过程归并起来,将这些过程中产生的计数答案也都存在一个通用的数组里。此数组根据题目具体要求设置状态,用递推或 DP 的方式进行状态转移。
统计答案可以选择记忆化搜索,也可以选择循环迭代递推。为了不重不漏地统计所有不超过上限的答案,要从高到低枚举每一位,再考虑每一位都可以填哪些数字,最后利用通用答案数组统计答案。
例题1
1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。对于所有评测用例,1 ≤ N ≤ 1018, 1 ≤ K ≤ 50。
对于N=7 K=2,二进制为111。
1、从最高位开始选,选0时0 _ _ ,最大值为011不会超过最大值,直接加上后两位可以全排列C(2, 2)。选1时1 _ _ 记录1的个数。
2、选第二位,选0时10 _ ,直接加上后一位的全排列 C(1, 2-1);选1时1的个数加1。
3、选最后一位11_ ,前面1的个数2 == k,答案加1。所以最后答案为C(2, 2) + C(1, 1) + 1 = 3
#include <iostream>
using namespace std;
typedef unsigned long long LL;
const int N = 64, M = 64;
LL n, k, c[N][N];
int main(){
for(int i=0; i<=N; i++){ //组合数公式,cij即i中选j个的个数
c[i][0] = 1;
for(int j=1; j<=i; j++)
c[i][j] = c[i-1][j-1] + c[i-1][j];
}
cin >> n >> k;
LL ans = 0, cnt = 0;
int a[M], st = 0;
while(n){ //取出每一位数字
a[++st] = n&1;
n = n>>1;
}
for(int i=st; i>0; i--){ //从高位到低位统计每一位数字可能出现的次数
if(i==1){ //单独处理最后一位
if(cnt==k || cnt+a[i]==k)//
ans++;
}
else if(a[i]){ //当前位为1
ans += c[i-1][k-cnt]; //选0时,那么剩下的i-1位可以全排列
++cnt; //选1时,统计已选的1的个数
}
}
printf("%lld",ans);
return 0;
}
例题2
给定两个正整数 aa 和 bb,求在 [a,b][a,b] 中的所有整数中,每个数码(digit)各出现了多少次。
递推实现
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 14;
LL a, b, cnta[N], cntb[N], dp[N];
void f(LL x, LL cnt[N]){//统计1~x之间每个数字出现的个数,并存储到cnt数组中
int dig[N], len = 0;//存储每一位上的数字
LL d = x;
do{//只有0也会统计成一位
dig[++len] = d % 10;
d /= 10;
}while(d);
for(int i=len; i; i--){//从高位往低位遍历,求出当前位所有数字出现的情况
for(int j=0; j<10; j++)//当前位不选最大值,dig[i]个i-1位全排列的数
cnt[j] += dp[i-1] * dig[i];
LL temp = pow(10.0, i-1);
for(int j=0; j<dig[i]; j++)//当前位0~dig[i],每个数有1e(i-1)种不同情况
cnt[j] += temp;
x -= temp * dig[i];//选最高位
cnt[dig[i]] += (x + 1);//当前位为dig[i]时,一共有x+1种情况
//if(i>1)
cnt[0] -= pow(10.0, i-1);
}
}
int main(){
for(int i=1; i<N; i++)//计算i位数字全排列时,每一位数字的个数
dp[i] = 10 * dp[i-1] + pow(10.0, i-1);
cin >> a >> b;
f(a-1, cnta);
f(b, cntb);
for(int i=0; i<10; i++){
if(i) cout << " ";
cout << cntb[i] - cnta[i];
}
cout << endl;
return 0;
}
递归
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 14;
LL a, b, cnta[N], cntb[N], dp[N], pw[N];
void dfs2(LL x, int high, LL ans[N]){//统计1到x
if(high==0) return;
int u = x / pw[high-1] % 10;
for(int i=0; i<u; i++) ans[i] += pw[high-1];
for(int i=0; i<10; i++) ans[i] += dp[high-1] * u;
x -= u * pw[high - 1];
ans[u] += (x+1);
ans[0] -= pw[high-1];
dfs2(x, high-1, ans);
}
int dig(LL x){//统计数字x的位数
int n = 0;
do{
n++;
x /= 10;
}while(x);
return n;
}
int main(){
pw[0] = 1;
for(int i=1; i<N; i++) {//初始化
pw[i] = pw[i-1] * 10;//pow(10, i)
dp[i] = dp[i-1]*10 + pw[i-1];//第i位的每一个数字的个数
}
cin >> a >> b;
dfs2(a-1, dig(a-1), cnta);
dfs2(b, dig(b), cntb);
for(int i=0; i<10; i++){
if(i) cout << " ";
cout << cntb[i] - cnta[i];
}
return 0;
}