数位dp的题目一般会问,某个区间内,满足某种性质的数的个数。

  1. 利用前缀和,比如求区间[l,r]中的个数,转化成求[0,r]的个数 [0,l-1]的个数。
  2. 利用树的结构来考虑(按位分类讨论)
    在这里插入图片描述
    P8764 [蓝桥杯 2021 国 BC] 二进制问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
const int N=70;
long long f[N][N];
long long n,k;
void init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) f[i][j]=1;
else f[i][j]=f[i-1][j]+f[i-1][j-1];
}

long long dp(long long n)
{
if(!n) return 0;
vector<int>vec;
while(n) vec.push_back(n%2),n/=2;
long long res=0,last=0;
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];
if(x)
{
res+=f[i][k-last];
last++;
if(last>k) break;
}
if(!i&&last==k) res++;
}
return res;
}

int main()
{
init();
cin>>n>>k;
cout<<dp(n)<<endl;
return 0;
}

加强一点,处理更多种进制:

1081. 度的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
using namespace std;
const int N=35;
int f[N][N],l,r,K,B;
//预处理组合数
int init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) f[i][j]=1;
else f[i][j]=f[i-1][j]+f[i-1][j-1];
}

int dp(int n)
{
if(!n) return 0;
vector<int>vec;
while(n) vec.push_back(n%B),n/=B;//十进制转成B进制
int res=0,last=0;
//res记录答案数,last表示用了多少个1
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];//取出第i位数
if(x)//(如果当前位x==0直接进入右分支,讨论下一位)
{
res+=f[i][K-last];
//当前位填0,从剩下的所有位(共有i位)中选K-last个数。
//对应于:左分支中0的情况,合法
if(x>1)
{
res+=f[i][K-last-1];
break;
}
else//x==1,直接讨论下一位,可用的1的个数减1
{
last++;
if(last>K) break;
}
}
if(i==0&&last==K) res++;//遍历到最后一位且最后一位取1
}
return res;
}
int main()
{
init();
cin>>l>>r>>K>>B;
cout<<dp(r)-dp(l-1)<<endl;
return 0;
}

1082. 数字游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
const int N=15;
int f[N][N],l,r;
//f[i][j]表示一共有i位,且最高位为j的数的个数

int init()
{
for(int j=0;j<=9;j++) f[1][j]=1;
for(int i=2;i<N;i++)
for(int j=0;j<=9;j++)
for(int k=j;k<=9;k++)
f[i][j]+=f[i-1][k];
}

int dp(int n)
{
if(!n) return 1;
vector<int>vec;
while(n) vec.push_back(n%10),n/=10;
int res=0,last=0;
//res记录答案数,last表示上一位的数字
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];//取出第i位数
if(last>x) break;//这一位数无论怎么取都比上一位小
for(int j=last;j<vec[i];j++)//进入左分支讨论
res+=f[i+1][j];
last=x;//更新lat
if(!i) res++;//到了最后一位,剩下一种合法的情况
}
return res;
}
int main()
{
init();
while(cin>>l>>r)
cout<<dp(r)-dp(l-1)<<endl;
return 0;
}

1083. Windy数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>
using namespace std;
const int N=11;
int f[N][N],l,r;
//f[i][j]表示一共有i位,且最高位为j的数的个数
//存的是(包含前导零)的情况
int init()
{
for(int j=0;j<=9;j++) f[1][j]=1;
for(int i=2;i<N;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
int dp(int n)
{
if(!n) return 0;
vector<int>vec;
while(n) vec.push_back(n%10),n/=10;
int res=0,last=-2;
//res记录答案数,last表示上一位的数字
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];//取出第i位数
for(int j=(i==vec.size()-1);j<x;j++)//首位不能取到零,其他位可以
if(abs(j-last)>=2) res+=f[i+1][j];
if(abs(x-last)>=2) last=x;
else break;
if(!i) res++;
}
for(int i=1;i<vec.size();i++)
for(int j=1;j<=9;j++)
res+=f[i][j];//特判首位为零的情况
return res;
}

int main()
{
init();
cin>>l>>r;
cout<<dp(r)-dp(l-1)<<endl;
return 0;
}

1084. 数字游戏 II

f[i][j][k] 表示一共有i位,且最高位数字是j,且所有位数字和%P结果为k的数的个数,若要转移到f[i][j][k]的状态,在i-1位对于每个x(x取值0~9)都应使第三维为(k-j)%P
状态转移方程:
$f[i][j][k]=\sum{k=0}^{P-1}\sum{x=0}^{9}f[i-1][x][(k-j)\%P]$

用last表示到当前为止,前面数位上的数字之和,对此,当前第i位数字为j,前面数字之和为last,那么
后i位(包括j这一位)数字之和sum与last的关系就是
(last+sum)%N==0,那么sum%N==(-last)%N,
所以res+=f[i+1][j][(-last%N)];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
using namespace std;
const int N=11;
int f[N][N][110],l,r,P;
//f[i][j][k]表示一共有i位,且最高位数字是j,且所有位数字和%P结果为k的数的个数
int mod(int u,int v)
{
return (u%v+v)%v;
}
int init()
{
memset(f,0,sizeof f);
for(int i=0;i<=9;i++) f[1][i][i%P]++;
for(int i=2;i<N;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<P;k++)
for(int x=0;x<=9;x++)
f[i][j][k]+=f[i-1][x][mod(k-j,P)];
}

int dp(int n)
{
if(!n) return 1;
vector<int>vec;
while(n) vec.push_back(n%10),n/=10;
int res=0,last=0;
//res记录答案数,last表示前面所有位数上数字的和
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];
for(int j=0;j<x;j++) //第i位放0~x-1
res+=f[i+1][j][mod(-last,P)]; //0~i位,所以一共有i+1位
last+=x;
if(!i&&last%P==0) res++;

}
return res;
}

int main()
{
while(cin>>l>>r>>P)
{
init();
cout<<dp(r)-dp(l-1)<<endl;
}
return 0;
}

1085. 不要62

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
using namespace std;
const int N=11;
int f[N][N],l,r;
//f[i][j]表示一共有i位,且最高位为j的数的个数

int init()
{
for(int j=0;j<=9;j++)
if(j!=4) f[1][j]=1//一位不含5
for(int i=2;i<N;i++)
for(int j=0;j<=9;j++)
{
if(j==4) continue;
for(int k=0;k<=9;k++)
{
if(k==4||(j==6&&k==2)) continue;
f[i][j]+=f[i-1][k];
}
}
}
int dp(int n)
{
if(!n) return 1;
vector<int>vec;
while(n) vec.push_back(n%10),n/=10;
int res=0,last=0;
//res记录答案数,last表示上一位的数字
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];//取出第i位数
for(int j=0;j<x;j++)
{
if(j==4||(j==2&&last==6)) continue;
res+=f[i+1][j];
}
if(x==4||(x==2&&last==6)) break;
last=x;
if(!i) res++;
}
return res;
}

int main()
{
init();
while(cin>>l>>r,l||r)
cout<<dp(r)-dp(l-1)<<endl;
return 0;
}

P8801 [蓝桥杯 2022 国 B] 最大数字

DFS,两个分支:用A处理/用B处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<bits/stdc++.h>
using namespace std;
long long n,ans;
int A,B;
vector<int>vec;

void dp(int index,int a,int b,long long res)
{
//cout<<"index:"<<index<<" A:"<<a<<" B:"<<b<<" res:"<<res<<endl;
if(index==-1)
{
//cout<<"***"<<endl;
ans=max(ans,res);
return;
}
int x=vec[index];
if(x==9)
dp(index-1,a,b,res*10+x);
if(a&&x!=9)
{
int pl=min(a,9-x);
dp(index-1,a-pl,b,res*10+x+pl);
}
if(b>=x+1&&x!=9)
dp(index-1,a,b-(x+1),res*10+9);
dp(index-1,a,b,res*10+x);
}

int main()
{
cin>>n>>A>>B;
while(n) vec.push_back(n%10),n/=10;
dp(vec.size()-1,A,B,0);
cout<<ans<<endl;
return 0;
}

P4317 花神的数论题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<bits/stdc++.h>
using namespace std;
const int mod=10000007;
long long n,ans=1,cnt;
const int N=70;
long long f[N][N],num[N];
vector<int>vec;

void init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) f[i][j]=1;
else f[i][j]=f[i-1][j]+f[i-1][j-1];
}

long long ksm(long long base,long long power)
{
base%=mod;
long long res=1;
while(power)
{
if(power&1)
res=res*base%mod;
power>>=1;
base=base*base%mod;
}
return res;
}

long long dp(long long n)
{
while(n) vec.push_back(n&1),n>>=1;
for(int i=vec.size()-1;i>=0;i--)
{
int x=vec[i];
if(x)
{
for(int j=1;j<=i;j++)
num[cnt+j]+=f[i][j];
++num[++cnt];
}
}
for(int i=0;i<vec.size();i++)
ans*=ksm(i+1,num[i+1]),ans%=mod;
return ans%mod;
}

int main()
{
init();
cin>>n;
cout<<dp(n)<<endl;
return 0;
}

P6218 [USACO06NOV] Round Numbers S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

#include<bits/stdc++.h>
using namespace std;
int l,r;
const int N=35;
int f[N][N];
//预处理组合数
void init()
{
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
if(!j) f[i][j]=1;
else f[i][j]=f[i-1][j]+f[i-1][j-1];
}

int get(int x)
{
vector<int> num;
while(x) num.push_back(x%2),x/=2;
int n=num.size();
int res=0, last=0;
for(int i=n-2;i>=0;i--)
{
if(num[i]) for(int j=max((n+1)/2-(last+1),0);j<=i;j++) res+=f[i][j];
else last++;
if(!i&&last>=(n+1)/2) res++;
}
for(int i=1;i<n;i++)
for(int j=(i+1)/2;j<i;j++)
res+=f[i-1][j];
return res+1;
}
int main()
{
init();
cin>>l>>r;
cout<<get(r)-get(l-1)<<endl;
return 0;
}

P2602 [ZJOI2010] 数字计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL f[20],cnta[20],cntb[20],ten[20],l,r;

void solve(long long x,long long *cnt)
{
long long num[20]={0};
int len=0;
while(x) num[++len]=x%10,x=x/10;
for(int i=len;i>=1;i--)
{
for(int j=0;j<=9;j++)
cnt[j]+=f[i-1]*num[i];
for(int j=0;j<num[i];j++)
cnt[j]+=ten[i-1];
long long num2=0;
for(int j=i-1;j>=1;j--)
num2=num2*10+num[j];
cnt[num[i]]+=num2+1;
cnt[0]-=ten[i-1];
}
}

int main()
{
cin>>l>>r;
ten[0]=1;
for(int i=1;i<=15;i++)
{
f[i]=f[i-1]*10+ten[i-1];
ten[i]=10*ten[i-1];
}
solve(l-1,cnta);
solve(r,cntb);
for(int i=0;i<10;i++)
cout<<cntb[i]-cnta[i]<<' ';
return 0;
}