数位dp用来解决一个大范围内满足一些条件的算法
常常伴随着记忆化搜索一起用
dfs从最大的值开始往小搜索(这是重点)
题目一
代码中 f 的含义:
比如一个范围是1~6325
其中1~1000 和 1001 ~2000和2001 ~3000和 3001 ~4000和5001 ~6000中满足题意的个数是相同的(不需要多次算,算一个就可以了,碰到直接加),而4001 ~5000和6001 ~6325要单独算
那么当千位为6时下一位(百位)的范围只能是0~3
同理如果千位为6,百位为3时,下一位(十位)的范围只能是0~2
f=1:就是当前位包括前面的位都取到了上限(千位取到6的同时百位取到了3),那么下一位的范围是0~a[x]
f=0: 前面的位没同时取到上限
#include<iostream>
using namespace std;
typedef long long ll;
int a[30],t;
ll dp[30][2],x;
ll dfs(int x,bool if4,bool f)//统计没出现49的个数
{
//x表示位数
//if4表示上一位是不是4
if(!x)return 1;
if(!f&&dp[x][if4])return dp[x][if4];
int up=f?a[x]:9;//f=1,那么这位的取值范围位0~a[x],否则0~9
ll sum=0;
for(int i=0;i<=up;i++)
{
if(if4&&i==9)continue;
sum+=dfs(x-1,i==4,f&&i==up);
///f&&i==up的含义是如果这位前面的都是到上限了且这位也到了上限,那么状态传递下去
}
if(!f)dp[x][if4]=sum;
return sum;
}
ll solve(ll x)
{
int cnt=0;
while(x)//把数分解存入数组
{
a[++cnt]=x%10;
x/=10;
}
return dfs(cnt,0,1);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld",&x);
cout<<x-solve(x)+1<<endl;
//因为dfs中把0也算了进去,而题目的范围是1~x
}
return 0;
}
题目二
题目意思是给范围1~n求数中含13且数能被13整除的个数
#include<iostream>
using namespace std;
typedef long long ll;
ll n;
int dp[35][13][3],a[35];
//op==0: 不含13且上位不是1
//op==1: 不含13且上位为1
//op==2: 含13
//mod的作用模仿手算除法的方式把余数存下来乘10再加上下一位再除
int dfs(int x,int op,int mod,bool f)
{
if(!x)return op==2&&!mod;//如果含13且余数为0那么加1
if(!f&&dp[x][mod][op])return dp[x][mod][op];
int up=f?a[x]:9;
int sum=0;
for(int i=0;i<=up;i++)
{
if((op==1&&i==3)||op==2)sum+=dfs(x-1,2,(mod*10+i)%13,f&&i==up);
else sum+=dfs(x-1,i==1,(mod*10+i)%13,f&&i==up);
}
if(!f)dp[x][mod][op]=sum;
return sum;
}
void solve()
{
int cnt=0;
while(n)
{
a[++cnt]=n%10;
n/=10;
}
cout<<dfs(cnt,0,0,1)<<endl;
}
int main()
{
while(scanf("%lld",&n)!=EOF)solve();
return 0;
}
题目三
就是求满足a&b=0的个数
即化为二进制数后a为1的位置,b的只能是0
a为0的位置,b的可以为1可以为0
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[1000],t,k,dp[1000],x;
int dfs(int x,bool f)
{
if(!x)return 1;
if(!f&&dp[x])return dp[x];
int up=f?a[x]:1,sum=0;
for(int i=0;i<=up;i++)
{
if(i==0)sum+=dfs(x-1,f&&i==up);
//b的x这个位置为0,那么a的这个位置是多少都可以,直接加
else if(((k>>(x-1))&1)==0)sum+=dfs(x-1,f&&i==up);
//b的这个位置为1,那么a的这个位置如果是0,那么就加
}
if(!f)dp[x]=sum;
return sum;
}
int solve(int x)
{
int cnt=0;
while(x)
{
a[++cnt]=x%2;
x>>=1;
}
return dfs(cnt,1);
}
int main()
{
scanf("%d",&t);
while(t--)
{
memset(dp,0,sizeof dp);
scanf("%d%d",&k,&x);
cout<<solve(x)-1<<endl;
}
return 0;
}
还有一些数位dp的题传送门