动态规划之数位dp

数位dp,字面意思理解就是在数字的每一位上面去dp,动态规划一般有两种:递推,记忆化搜索(dfs)。这里就是用的记忆化。

一般这种用在计数上面,对那些数位上面有限制的计数。
这里上一道模板题:http://acm.hdu.edu.cn/showproblem.php?pid=3555

题中就是要你统计1—N里有“49”的个数。

dp[pos][sta]表示到第pos位,状态为sta的总数。
我们一般是从高位往低位计数。我们这里的状态本来可以是10种(0,1,2,。。。9)但是我们可以把他们分为两种(为4的,不为4的)
如果我们当前位为4,那么下一位就不能为9.

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define ll long long 
ll dp[20][2];
int digit[20];
//分解x,将各个位赋值给digit数组
ll solve(ll x){
	int pos=0;
	while(x){
		digit[++pos]=x%10;
		x/=10;
	}
	//这里limit为true,是因为我们第一位的前面想象为0,那么就是有上界了(0为上界)。
	return dfs(pos,false,true);
}
//pos表示当前第几位
//sta表示前一位是否是4
//limit表示是否到达上界 
ll dfs(int pos,bool sta,bool limit){
	//pos==0表示到达 个位 之后了 
	if(pos==0) return 1;
	//如果没有上界限制 并且dp值之前记忆化了(存过dp值) 
	if(!limit&&dp[pos][sta]) return dp[pos][sta];
	//cnt表示总数,up表示如果前面一位到上界了,那么我这一位也只能到digit[pos]。 
	ll cnt=0,up=limit?digit[pos]:9;
	for(int i=0;i<=up;i++){
		//如果sta(上一位为4) 并且这一位为9,剪枝 
		if(sta&&i==9) continue;
		//pos-1(下一位的情况) ,i==4(这一位是否为4,给下一位参考) ,上一位是否到达上界并且当前位达到上界 
		cnt+=dfs(pos-1,i==4,limit&&i==up);
	} 
	if(!limit) dp[pos][sta]=cnt;
	return cnt;
}
int main()
{
	std::ios::sync_with_stdio(false);
	int n;
	while(cin>>n){
		ll x;
		while(n--){
			cin>>x;
			//这里加一是因为solve()多算0...0(0)的情况
			printf("%lld\n",x+1-solve(x));
		}
	}
}



为什么返回值那里要if(!limit),我们假设上界是230,求1–230的不含“33”的个数。那么我们百位访问0的时候,因为没有达到上界(2),所以十位我们可以访问0-9,当十位访问3的时候,个位我们可以访问0,1,2,4(不能有“33”),所以有4个。但是当我们百位访问2的时候,到达上界了,就是说十位,个位我们不能取0-9了,十位只能取到3,个位只能取到0。当我们十位取到3的时候,这是如果我们没有(!limit),我们前面算到dp[0][1](当前位 为 个位,前面一位是3),我们会直接返回4,但是这里我们只能访问0,因为不能超过230。所以得limit。
后面的记忆化也要limit也是这样。

HDU 4507
题目要求的是与7无关的数的平方和。这个数位dp不是计数的,而是求平方和的。
dp[pos][mod1][mod2]为当前位为pos,各个位数的和模7,数的和模7
首先我们先设置状态,题目的要求:
1,没有7,这个直接在循环里面判断进行了.
2.各位数之和不是7的倍数
3.这个数不是7的倍数。
2.3这两个得在递归里面去加,然后分情况。
可以对7取模,就有余数为0,就是7的倍数,不为0,就是可以找的数。
这是条件,接下来看要求的平方和。
我们算一个数的平方和,比如123,145,134这些都是合法的数,那求平方和就是,(100+23)^2+(100+45) ^2+(100+34) ^2=3100 ^2+2100*(23+45+34)+(23 ^2+45 ^2+34 ^2);
这样我们假设我们求到pos为百位1的时候,我们求的平方和就为
从十位开始后面的数(加上百位后的各种条件合法后)的个数乘以
ipower[i]+ 2 * i power[i] * 十位后的数的总和 +十位后数的平方和。

temp为低位,ans为高位。可以看做temp为十位后面的,ans为百位
其实就是:
ans.cnt+=temp.cnt (合法的数)

ans.sum1+=temp.cnt * i * power[pos]+temp.sum1 (各个数的和)

ans.sum2+=i * power[pos] * i * power[pos] * temp.cnt+temp.sum2+2 * i * power[pos] * temp.sum1(各个数平方和)
为什么要乘以个数,就是为百位1后面 有多个合法的数。
其他的都和简单的数位dp差不多了。
要注意的点
solve(y)可能比solve(x-1)小,所以要+mod后取模,比如(6-3)%5=3
,但是6%5-3%5=-2, (-2+5)%5=3;

#include 
#include 
#include 
#include 
#include 
#define ll long long
#define mod 1000000007
using namespace std;
struct node{
	ll cnt,sum1,sum2;
}dp[20][10][10]; 
int digit[20];
ll power[25];
node dfs(int pos,int mod1,int mod2,bool limit){
	if(!pos){
		node temp={0,0,0};
		if(mod1&&mod2) temp.cnt=1;
		return temp;
	}
	if(!limit&&dp[pos][mod1][mod2].cnt!=-1) return dp[pos][mod1][mod2];
	int up=limit?digit[pos]:9;
	node ans={0,0,0};
	for(int i=0;i<=up;i++){
		if(i==7) continue;
		node cur=dfs(pos-1,(mod1+i)%7,(mod2*10+i)%7,limit&&i==up);
		ans.cnt=(ans.cnt+cur.cnt)%mod;
		ans.sum1+=(cur.cnt*i%mod*power[pos]%mod+cur.sum1)%mod;
		ans.sum1%=mod;
		ans.sum2+=(((i*power[pos])%mod*(i*power[pos])%mod*cur.cnt%mod+cur.sum2)%mod+2*(i*power[pos])%mod*cur.sum1%mod)%mod;
		ans.sum2%=mod;
	}
	if(!limit)  dp[pos][mod1][mod2]=ans;
	return ans;
}
ll solve(ll x){
	int pos=0;
	while(x){
		digit[++pos]=x%10;
		x/=10;
	}
	return dfs(pos,0,0,true).sum2%mod;
}
int main()
{
	std::ios::sync_with_stdio(false);
	int n;
	memset(dp,-1,sizeof(dp));
	power[1]=1;
	for(int i=2;i<=20;i++) power[i]=power[i-1]*10%mod;
	while(~scanf("%d",&n)){
		while(n--){
			ll x,y;
			scanf("%lld %lld",&x,&y);
			//这里取模后solve(y)可能比solve(x-1)小,所以这样处理
			printf("%lld\n",(solve(y)-solve(x-1)+mod)%mod);
		}
	}
}



你可能感兴趣的:(动态规划)