7.5 数位DP

7.5 数位DP

所谓数位DP,就是某些问题如果以数作为状态的属性进行转移,数值过大会导致时间和空间的复杂度过高。然而问题中n位数的情况可以由i位数(1<=i<n)的情况直接得到,于是我们可以以数的长度和数某位的值作为状态的属性进行转移。

下面以一道题为例。


hdu 2089 “不要62”

一个数字,如果包含4或者62,它是不吉利的。给定m和n,统计m~n中吉利数的个数。

分析:如果直接用dp[i]表示1-i,当n的值过大时显然复杂度太高。考虑用数位dp,对于长度为i的符合条件的数,在它的首位之前加上1,2,3,5,7,8,9都一定能构成一个长度为i+1的符合条件的数;如果长度为i的符合条件的数首位不是2,那么首位加上6也是一个长度为i+1的符合条件的数。

那么我们定义状态dp[i][j],表示i位数中首位是j符合条件的数的个数(j能够为0),那么有状态转移方程如下:

dp[i][j]=Σdp[i-1][k],0<=k<=9,j≠4,k≠2&&j≠6。

这么做比起直接以数作为状态的属性进行dp的复杂度要小很多。

#include<cstdio>
using namespace std;
const int len=18;//表示数字的最大长度 
int dp[len+1][10];//dp[i][j]表示i位数,第一个数是j时符合条件的数字数量
int digit[len+1];//digit[i]存储n的第i位数字
void init(){dp[0][0]=1;
	for (int i=1;i<=len;i++) for (int j=0;j<10;j++) for (int k=0;k<10;k++){
		if ((j!=4)&&(k!=2&&j!=6)) dp[i][j]+=dp[i-1][k];//i位数的首位不能等于4, 
	}
}//计算[0,n]区间满足条件的数字个数 
int solve(int l){int ans=0;
	for (int i=l;i>=1;i--){//从高位到低位进行处理
		//例如n=324561,首先就要加上第一个数为0,1,2的满足条件的六位数,这样我们就能得到0-299999的符合条件的数的个数
		//再求300000-324561的符合条件的数的个数,就相当于求0-24561的符合条件的数的个数
		//同理接下来就是求320000-324561的数的个数,就是相当于0-4561的数的个数
		//但此时如果要求324000-324561的符合条件的数的个数,因为第三位一定为4,那么显然后面的数都不符合条件,直接break 
		for (int j=0;j<digit[i];j++) if (j!=4) ans+=dp[i][j]; 
		if (digit[i]==4){ans--; break;}
		if (i>1&&digit[i]==6&&digit[i]==2){ans--; break;} 
	} return ans; 
}
int main(){int a,b,l1,l2,A,B; init(); 
	while(scanf("%d%d",&a,&b)&&(a||b)){a--; l1=0; l2=0;
		while (a){digit[++l1]=a%10; a/=10;} A=solve(l1);
        while (b){digit[++l2]=b%10; b/=10;} B=solve(l2);
		printf("%d\n",B-A);
	}
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值