数位dp

数位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的题传送门

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值