数位dp的学习
经典问题模型
- 给定一个闭区间 [L,R],求区间中满足某条件的数的
例题学习
- 题目描述: 给定一个区间 ,求其中,将数位拆分后,相邻两个数字相差至少为2的数的个数
- 问题分析:
- 利用差分的思想,把求区间 [L,R] 中满足条件的数的个数看成求区间 [ 0 , R ] [0,R] [0,R] 中满足条件的数的个数减去区间 [ 0 , L − 1 ] [0,L-1] [0,L−1] 中满足某条件的个数,即 a n s r − a n s l − 1 ans_r - ans_{l-1} ansr−ansl−1
- 其次考虑时间复杂度:用循环的时间复杂度: O ( n l g n ) O(nlgn) O(nlgn);而用 dfs 去枚举每一位的值,则时间复杂度为:O(n)。 要知道搜索时是可以直接按照满足条件的情况下进行 dfs,会有很大程度上的剪枝的,所以我们毫不犹豫的选择了 dfs 的写法。
- 如何用 dfs 来写?
数位dp原理
基本原理: 数位dp其实就是用dfs加状态加记忆化搜索的方法实现一个dp
思路分析
- 假设 n 的每一位分别为 a[i]
- 分析一下每一位:
- 最高位:[0,a[i]]
- 次高位:当最高位取a[i]时,次高位只能取 [0,a[i-1]]
当最高位不取a[i]时,次高位可以取 [0,9] - 非最高位:只要其高位有一个没取a[i],该位就可以任取 [0,9]
状态设置
- dfs设置三个状态:x,st,op,
- 设最低位为第 1 位,dfs从高位到低位
- x:判断第x位要选什么数
- st:前缀状态,通常为上一位置选择的数。由于最高位没有上一位,为方便起见,有时我们设:当 st = 10时,暂时未出现最高位。
- op:
- 当op=1,其所有高位均选择了最大值,第x位只能选 [0,a[x]]
- 当op=0,其高位有一位没选择最大值,第x位可选 [0,9]
当没有任何状态限制时,根据题意我们写出了以下代码,刚好遍历了 0-n 的所有数,无一重复,也无一越界。
当我们需要有状态限制时,加入一个注释掉的check函数判断限制条件。成功遍历到最后一位则该数可以,计数。
数位dp模板
模板如下
#include <bits/stdc++.h>
using namespace std;
vector<int>vec;
long long f[70][15];
long long dfs(int x,int st,int op) {
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0)return 1;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0;i<=up;i++){
ret+=dfs(x-1,i,op&(i==up));
}
if(op==0)f[x][st]=ret;
return ret;
}
long long run(long long n) {
memset(f,-1,sizeof(f));
vec.clear();
vec.push_back(-1);
while(n) {
vec.push_back(n%10);
n=n/10;
}
return dfs(vec.size()-1,11,1);
}
long long run(long long n) {
memset(f,-1,sizeof(f));
vec.clear();
vec.push_back(-1);
while(n) {
vec.push_back(n%2);
n=n/2;
}
return dfs(vec.size()-1,0,1);
}
字符串版
- 如果 L,R 的数字很大,那么要通过字符串形式进行数位 dp ,但是我们不好计算 L-1。不妨计算 [ 0 , R ] − [ 0 , L ] + c h e c k ( L ) [0,R]-[0,L]+check(L) [0,R]−[0,L]+check(L)。如果传参的话记得传指针,别直接传字符串。例如:传参 string &
记忆化搜索优化
-
状态 {x,st,op} 的 d f s dfs dfs 分支会不止走一次!!!
但是往后 d f s dfs dfs 过程得到的总答案数是相等的 -
例如上面例题中:假设 n 为5位数,且最高位为5,则12—和22—在dfs均走到了dfs(3,2,0)这个状态,且该状态往后的所有dfs分支均一致,没有必要重新计算一遍。
-
即:当我们走完dfs(x,st,op)这个状态的时候,我们记录一个 f [ x ] [ s t ] [ o p ] f[x][st][op] f[x][st][op] 往后走得到的答案总数,则下一次走到 {x,st,op} 的时候不需要再次计算。
-
由于 op = 1 的 {x,st,op} 分支一定只有一个,因此我们不记录。只用一个数组 f [ x ] [ s t ] f[x][st] f[x][st] 来记录 {x,st,0} 的分支
前导0的影响
- 对于出现了前导 0 和未出现前导 0 ,其状态 x , s t {x,st} x,st 往后的分支可能是不一样的,要注意下。
例题的完整代码如下
#include<bits/stdc++.h>
using namespace std;
vector<int>vec;
long long f[70][15];
long long dfs(int x,int st,int op) {
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0)return 1;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0;i<=up;i++){
if(abs(st-i)<2)continue;
if(st==11&&i==0)ret+=dfs(x-1,11,op&(i==up));
else ret+=dfs(x-1,i,op&(i==up));
}
if(op==0)f[x][st]=ret;
return ret;
}
long long run(long long n) {
memset(f,-1,sizeof(f));
vec.clear();
vec.push_back(-1);
while(n) {
vec.push_back(n%10);
n=n/10;
}
return dfs(vec.size()-1,11,1);
}
int main() {
long long l,r;
cin>>l>>r;
cout<<run(r)-run(l-1)<<endl;
return 0;
}
例题汇总
数位之间相互限制,计算满足某条件的个数
例题1:不出现 62 个数的个数
- 题目描述:不要62。求区间 [l,r] 中数位不出现62的数的个数。例如:621,1362均出现62,1263没出现62。
- 问题分析:前缀状态 st 存储上一个数,st=6 且 i=2 则该数出现62,婷婷!成功遍历到最后一位则该数符合题意,计数。
- 记忆化: f [ x ] [ s t ] f[x][st] f[x][st] 存储状态 {x,st} 往后的所有符合条件的数的个数。举个栗子:当op=0 时,123–和213–往后符合条件的数的个数一样,没必要再次计算。
int dfs(int x,int st,int op){
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0)return 1;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0;i<=up;i++){
if(st==6&&i==2)continue;
ret+=dfs(x-1,i,op&(i==up));
}
if(op==0)f[x][st]=ret;
return ret;
}
例题2:不存在相邻数相同的数
- 题目描述: 求区间 [l,r] 的每个数中,其数位不存在相邻数相同的数的个数。
- 问题分析:前缀状态 st 存储上一个数,i==st,婷婷!成功遍历到最后一位则该数符合题意,计数。
- 记忆化: f [ x ] [ s t ] f[x][st] f[x][st] 存储状态 {x,st} 往后的所有符合条件的数的个数。举个栗子:当op=0 时,123–和213–往后符合条件的数的个数一样,没必要再次计算。
- 注意: 未出现首非 0 元素之前,st = 10
int dfs(int x,int st,int op){
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0)return 1;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0;i<=up;i++){
if(st==i)continue;
if(i==0&&st==10)ret+=dfs(x-1,10,op&(i==up));
else ret+=dfs(x-1,i,op&(i==up));
}
if(op==0)f[x][st]=ret;
return ret;
}
void solve(){
dfs(vec.size()-1,10,1);
}
例题3:二进制下的数位中,0的个数不小于1的个数的数的个数
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 的每个数中,其二进制下的数位里,0的个数不小于1的个数的数的个数。
- 问题分析: 以二进制进行数位拆分预处理,前缀状态设置两个,分别为到当前位记录的 0 的数量 st0 和 1 的数量 st1 。dfs 完所有位后判断一下 st0 和 st1 是否符合题意即可,符合则计数。
- 记忆化:当op=0时,110–和100–往后符合条件的数的个数是一样的,没必要重复计算。设 f [ x ] [ s t 0 ] [ s t 1 ] f[x][st0][st1] f[x][st0][st1] 为该 st0,st1 状态往后符合条件的数的个数。
- 注意:不要把前导 0 算进了 st0 里了
long long dfs(int x,int st0,int st1,int op) {
if(op==0&&f[x][st0][st1]!=-1)return f[x][st0][st1];
if(x==0){
if(st0>=st1)return 1;
else return 0;
}
int up=op?vec[x]:1;
long long ret=0;
for(int i=0; i<=up; i++) {
if(st1==0){
if(i==0)ret+=dfs(x-1,st0,st1,op&(i==up));
else ret+=dfs(x-1,st0,st1+1,op&(i==up));
}
else {
if(i==0)ret+=dfs(x-1,st0+1,st1,op&(i==up));
else ret+=dfs(x-1,st0,st1+1,op&(i==up));
}
}
if(op==0)f[x][st0][st1]=ret;
return ret;
}
void solve(){
dfs(vec.size()-1,0,0,1);
}
例题4:数位中非 0 数不超过 3 个的数的个数
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 中,数位中非 0 数不超过 3 个的数的个数
- 问题分析: st 记录非 0 个数即可。
long long dfs(int x,int st,int op){
if(op==0&&f[x][st]!=-1)return f[x][st];
if(st>3)return 0;
if(x==0)return 1;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0;i<=up;i++){
if(i==0)ret+=dfs(x-1,st,op&(i==up));
else ret+=dfs(x-1,st+1,op&(i==up));
}
if(op==0)f[x][st]=ret;
return ret;.m
}
例题5:数位中不同数字不超过 k 个的数的和(状压)
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 中,数位中不同数字不超过 k 个的数的和。 r < = 1 e 18 , k < = 10 r<=1e18,k<=10 r<=1e18,k<=10。
- 问题分析: 状压记录状态,用 st 的第 i 位表示数字 i 出现情况,然后记忆化即可。
数位之间无限制,统计所有数的某特征(该特征可以边枚举第 x 位的值边算)
例题1:数位和之和
- 题目描述: 求区间 [l,r]中每个数的数字和的和,答案mod 1e9+7。例如:123的数字和是1+2+3=6
- 问题分析: 状态 st 存储到当前位数的所有数字之和 (123-,此时st=6),dfs到最后一位则返回 st。
- 记忆化:当op = 0时,12–和21–往后走的所有分支数的数字和都是一样的,因为所有的分支的数字和都加上6也就是 st。 f [ x ] [ s t ] f[x][st] f[x][st] 记忆化存储状态 {x,st} 为从 x 往后的所有分支的数字和。
long long dfs(int x,int st,int op) {
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0)return st;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0; i<=up; i++) {
ret=(ret+dfs(x-1,st+i,op&(i==up)))%mod;
}
if(op==0)f[x][st]=ret;
return ret;
}
例题2:二进制下 1 的个数的乘积
- 题目描述: 求区间 [1,n] 的每个数中,其二进制下1的数目的乘积,答案mod 1e7+7。例如: n = 4 , a n s = 1 × 1 × 2 × 1 = 2 n=4,ans=1\times 1\times 2\times1=2 n=4,ans=1×1×2×1=2
- 问题分析: 以二进制进行数位拆分预处理,前缀状态 st 存储到当前位记录的1的数目,dfs 完所有位后判断乘上 st 即可,要注意的是 0 要特判。
- 记忆化: 当 op = 0,110–和100-- 往后的所有数的 st 之积是一样的,没必要重复计算。设 f [ x ] [ s t ] f[x][st] f[x][st] 为状态 {x,st} 往后的所有数的st之积。
#include<bits/stdc++.h>
using namespace std;
vector<int>vec;
long long f[100][100],mod=1e7+7;
long long dfs(int x,int st,int op) {
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==0){
if(st!=0)return st;
else return 1;
}
int up=op?vec[x]:1;
long long ret=1;
for(int i=0;i<=up;i++){
if(i==1)ret=(ret*dfs(x-1,st+1,op&(i==up)))%mod;
else ret=(ret*dfs(x-1,st,op&(i==up)))%mod;
}
if(op==0)f[x][st]=ret;
return ret;
}
例题3:0-9 在每个数的数位中一共出现多少次
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 的每个数中,每个数字各出现了多少次。例如:[12-15],1出现4次,2,3,4,5出现1次
- 问题分析: 前缀状态 st 自然要存储到当前位下,0-9各出现了多少次?那前缀状态st要存储10个数值?那记忆化搜索的状态设置怎么办?换个思路,我们可以分别计算 0-9 在数位中一共出现多少次,每次数位 dp 只去计算一个数字在所有数中出现的次数。前缀状态 st 存储到当前位下某数字 key 出现的次数, d f s dfs dfs 完所有位后返回 st 计数即可
- 记忆化: f [ x ] [ s t ] f[x][st] f[x][st] 该状态往后的所有数中 key 出现的总次数。
- 注意: 计算 0 的时候,前导 0 不要算进去了
long long dfs(int x,int st,int key,int bool1,int op) {
if(op==0&&f[x][st][bool1]!=-1)return f[x][st][bool1];
if(x==0)return st;
int up=op?vec[x]:9;
long long ret=0;
for(int i=0; i<=up; i++) {
if(key==0) {
if(i==0) {
if(bool1==0)ret+=dfs(x-1,st,key,bool1,op&(i==up));
else ret+=dfs(x-1,st+1,key,bool1,op&(i==up));
} else {
ret+=dfs(x-1,st,key,1,op&(i==up));
}
} else {
if(i==key)ret+=dfs(x-1,st+1,key,bool1,op&(i==up));
else ret+=dfs(x-1,st,key,bool1,op&(i==up));
}
}
if(op==0)f[x][st][bool1]=ret;
return ret;
}
long long run(long long n,int key) {
memset(f,-1,sizeof(f));
vec.clear();
vec.push_back(-1);
while(n) {
vec.push_back(n%10);
n=n/10;
}
return dfs(vec.size()-1,0,key,0,1);
}
int main() {
long long l,r;
cin>>l>>r;
for(int i=0; i<=9; i++) {
cout<<run(r,i)-run(l-1,i)<<" ";
}
return 0;
}
例题4: ∑ i = 0 A \sum_{i=0}^A ∑i=0A ∑ j = 0 B \sum_{j=0}^B ∑j=0B ( l o g 2 ( i + j ) + 1 ) (log2(i+j)+1) (log2(i+j)+1)[i&j==0](二维)
- 题目描述: 给定 A,B,计算 ∑ i = 0 A \sum_{i=0}^A ∑i=0A ∑ j = 0 B \sum_{j=0}^B ∑j=0B ( l o g 2 ( i + j ) + 1 ) (log2(i+j)+1) (log2(i+j)+1)[i&j==0] , 且 i,j 不同时为 0 , 0<=A,B<=1e9
- 问题分析: 从一维数位到二维数位。考虑存储的状态,对于 l o g 2 ( i + j ) + 1 log2(i+j)+1 log2(i+j)+1 和[1&j==0],很容易想到二进制,由于 i&j = 0,在二进制下 i+j 必然不进位,则 log2(i+j)+1的值就取决于 i+j 的二进制下最高位1所在的位置。那么 dfs 的状态就来了!!!并且同时要保证 i,j 二进制下的每一位都不同。
- 很显然,二进制下的数位dp。状态{x,st1,st2,fir}为 i 的第 x 位取 st1,j 的第 x 位取 st2,fir 为最高位 1 所在位置。限制条件为第 x 位的 i 和 j 不同时取1。 d f s dfs dfs 到尽头表示这对数可以,返回 fir 。
- 记忆化:懂的都懂
#include<bits/stdc++.h>
using namespace std;
vector<int>vec1,vec2;
long long mod=1e9+7;
long long f[100][2][2][100];
long long dfs(int x,int st1,int op1,int st2,int op2,int fir){
if(op1==0&&op2==0&&f[x][st1][st2][fir]!=-1)return f[x][st1][st2][fir];
if(x==0)return fir;
long long ret=0;
int up1=op1?vec1[x]:1;
int up2=op2?vec2[x]:1;
for(int i=0;i<=up1;i++){
for(int j=0;j<=up2;j++){
if(i+j==2)continue;
if(fir==0&&i^j!=0)fir=x;
ret+=dfs(x-1,i,op1&(i==up1),j,op2&(i==up2),fir);
}
}
if(op1==0&&op2==0)f[x][st1][st2][fir]=ret;
return ret;
}
long long run(long long A,long long B){
vec1.push_back(-1);
vec2.push_back(-1);
memset(f,-1,sizeof(f));
while(A||B){
vec1.push_back(A%2);
vec2.push_back(B%2);
A=A/2;
B=B/2;
}
cout<<vec1.size()-1;
return dfs(vec1.size()-1,1,1,1,1,0);
}
int main(){
int A,B;
cin>>A>>B;
cout<<run(A,B);
return 0;
}
与 m|x 的值有关的数位 dp
套路
- 预处理 g [ i ] [ j ] = 1 0 i ( m o d j ) g[i][j]=10^i\pmod j g[i][j]=10i(modj),这样枚举数位的时候,就可以快速计算出 ( m o d m ) \pmod m (modm) 下的值,从而记忆化 dp 。
例题1: ∑ x = L R [ x ≡ f ( x ) ( m o d m ) ] \sum_{x=L}^R [x\equiv f(x)\pmod m] ∑x=LR[x≡f(x)(modm)], f ( x ) f(x) f(x) 为 x 的两两数位乘积和
- 题目描述: 求区间 [ L , R ] [L,R] [L,R] 中,满足 x ≡ f ( x ) ( m o d m ) x\equiv f(x)\pmod m x≡f(x)(modm) 的 x 的个数,答案 ( m o d 1 e 9 + 7 ) \pmod {1e9+7} (mod1e9+7)。 10 < = L < = R < = 1 0 5000 , 2 < = m < = 60 。 10<=L<=R<=10^{5000},2<=m<=60。 10<=L<=R<=105000,2<=m<=60。
- 问题分析: x ≡ f ( x ) ( m o d m ) < = > 0 ≡ f ( x ) − x ( m o d m ) x\equiv f(x)\pmod m<=> 0\equiv f(x)-x\pmod m x≡f(x)(modm)<=>0≡f(x)−x(modm)。设 r e t s u m = f ( x ) − x ( m o d m ) retsum=f(x)-x\pmod m retsum=f(x)−x(modm) , g [ i ] [ j ] g[i][j] g[i][j] 为 1 0 j ( m o d i ) 10^j\pmod i 10j(modi),记录到第 x 位的数位和 s u m ( m o d m ) sum\pmod m sum(modm) ,这样枚举第 x 位的值时,就可以快速计算 r e t s u m retsum retsum 和 s u m sum sum 。记忆化 f [ x ] [ s u m ] [ r e t s u m ] f[x][sum][retsum] f[x][sum][retsum] 即可,思考发现其之后的分支是相同的。考虑到 L,R 值很大,需要用字符串形式存储。
#include <bits/stdc++.h>
using namespace std;
int T, n, m,f[5100][61][61],g[5100][61],mod=1e9+7;
string L,R;
int dfs(int x,int op,int sum,int retsum,string &s){
if(op==0&&f[x][sum][retsum]!=-1)return f[x][sum][retsum];
if(x==s.size()){
if(retsum==0)return 1;
else return 0;
}
int up=op?s[x]-'0':9;
int ret=0;
for( int i=0;i<=up;i++){
ret=(ret+dfs(x+1,op&(i==up),(sum+i)%m,((retsum+sum*i-i*g[s.size()-x-1][m])%m+m)%m,s))%mod;
}
if(op==0)f[x][sum][retsum]=ret;
return ret;
}
int check(string &s){
int sum=0,retsum=0;
for( int i=0;i<s.size();i++){
retsum=(retsum+sum*(s[i]-'0')-(s[i]-'0')*g[s.size()-i-1][m])%m;
sum=(sum+s[i]-'0')%m;
}
if(retsum==0)return 1;
else return 0;
}
int solve(string &s){
for( int i=0;i<=s.size();i++){
for( int j=0;j<=m;j++){
for( int k=0;k<=m;k++){
f[i][j][k]=-1;
}
}
}
return dfs(0,1,0,0,s);
}
int main()
{
for(int i=2;i<=60;i++){
g[0][i]=1;
for(int j=1;j<=5000;j++){
g[j][i]=g[j-1][i]*10%i;
}
}
cin>>L>>R>>m;
cout<<((solve(R)-solve(L)+check(L))%mod+mod)%mod<<endl;
}
例题2:m|x 且 x 的偶数位为 d,奇数位不为 d 的数的个数
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 中,m|x 且 x 的偶数位为 d,奇数位不为 d 的数 x 的个数。 r < = 1 0 2000 , 1 < = m < = 2000 , 0 < = d < = 9 r<=10^{2000},1<=m<=2000,0<=d<=9 r<=102000,1<=m<=2000,0<=d<=9 。
- 问题分析: 直接根据套路枚举数位即可,记录 f [ x ] [ s t ] f[x][st] f[x][st] 为到第 x 位时,前面的数对 m 的取余为 st 。
#include <bits/stdc++.h>
using namespace std;
int m,d,f[2100][2100],g[2100],mod=1e9+7;
string L,R;
int dfs(int x,int op,int st,string &s) {
if(op==0&&f[x][st]!=-1)return f[x][st];
if(x==s.size()) {
if(st==0)return 1;
else return 0;
}
int up=op?s[x]-'0':9,ret=0;
if(x%2==0){
for(int i=0;i<=up;i++){
if(i!=d)ret=(ret+dfs(x+1,op&(i==up),(st+i*g[s.size()-x-1])%m,s))%mod;
}
}
else{
if(d<=up)ret=(ret+dfs(x+1,op&(d==up),(st+d*g[s.size()-x-1])%m,s))%mod;
}
if(op==0)f[x][st]=ret;
return ret;
}
int check(string &s) {
long long value=0;
for(int i=0; i<s.size(); i++) {
if(i%2==0) {
if(s[i]-'0'==d)return 0;
} else {
if(s[i]-'0'!=d)return 0;
}
value=(value*10+s[i]-'0')%m;
}
if(value==0)return 1;
else return 0;
}
int solve(string &s) {
for(int i=0; i<=s.size(); i++) {
for(int j=0; j<=m; j++) {
f[i][j]=-1;
}
}
return dfs(0,1,0,s);
}
int main() {
cin>>m>>d;
g[0]=1%m;
for(int i=1; i<=2000; i++)g[i]=g[i-1]*10%m;
cin>>L>>R;
cout<<((solve(R)-solve(L)+check(L))%mod+mod)%mod;
return 0;
}
例题3:x 的数位均整除 x 的数的个数
- 题目描述: 求区间 [ l , r ] [l,r] [l,r] 中,数 x 的数位均整除 x 的数的个数。 r < = 1 e 18 r<=1e18 r<=1e18
- 问题分析: 假设数 x 只出现 {a1,a2,a3},那么 x 就需要能整除 lcm(a1,a2,a3) ,而这个过程我们是可以轻松计算的。不妨我们枚举 x 出现的数字,然后根据整除这些数字的最大公约数来进行数位 dp 。
多次查询
思路(下面例子与思路没有直接关系,仅多次查询)
- 在出现样例较多,状态也多的情况时。不管是初始化 f 为 -1,还是数位 dp 的部分,其时间复杂度都超时。
- 思考我们记录的状态 {x,st,op=0} 表示的意思:其表示数位中第 1 位到第 x 位在没有任何上届限制的情况下。在多次查询,且查询问题一样的情况下,是不需要任何初始化的,因为上次求得的 f 数组,下次还是能用。
例题1:数位中每个数字都出现偶数次的数的个数(状压)
- 题目描述: q 次询问,求区间 [ l , r ] [l,r] [l,r] 中,b 进制下数位中每个数字都出现偶数次的数的个数。 r < = 1 0 18 , q < = 1 e 5 , 2 < = b < = 10 r<=10^{18},q<=1e5,2<=b<=10 r<=1018,q<=1e5,2<=b<=10
- 问题分析: 状压记录状态,用 st 的第 i 位表示数字 i 的出现次数的奇偶性,然后记忆化即可。注意要处理前导 0 。到第 x 位,且前面状态为 st ,该位是否能取最大之后的分支是不一样的,出现前导 0 和未出现前导 0 的之后的分支耶是不一样的。
#include <bits/stdc++.h>
using namespace std;
vector<int>vec;
long long f[80][1025][11],b;
long long dfs(int x,int st,int op,int key) {
if(op==0&&key==1&&f[x][st][b]!=-1)return f[x][st][b];
if(x==0){
if(st==0&&key==1)return 1;
else return 0;
}
int up=op?vec[x]:b-1;
long long ret=0;
for(int i=0;i<=up;i++){
if(key==0&&i==0)ret+=dfs(x-1,st,op&(i==up),0);
else ret+=dfs(x-1,st^(1<<i),op&(i==up),1);
}
if(op==0&&key==1)f[x][st][b]=ret;
return ret;
}
long long run(long long n) {
if(n==0)return 0;
vec.clear();
vec.push_back(-1);
while(n) {
vec.push_back(n%b);
n=n/b;
}
return dfs(vec.size()-1,0,1,0);
}
int main(){
memset(f,-1,sizeof(f));
long long T,l,r;
cin>>T;
while(T--){
cin>>b>>l>>r;
cout<<run(r)-run(l-1)<<endl;
}
return 0;
}