数位dp,就是和数位有关的dp(废话),一般用来计数/求和
数位dp用记忆化搜索来写比较容易理解点,我们之后就都用记忆化搜索来写了
既然从高位往低位枚举,那么状态一般都是与前面已经枚举的数位有关并且通常是根据约束条件当前枚举的这一位能使得状态完整(比如一个状态涉及到连续k位,那么就保存前k-1的状态,当前枚举的第k个是个恰好凑成成一个完整的状态,不过像那种状态是数位的和就直接保存前缀和为状态了)
1. C - 不要62 HDU - 2089
这个题的数据范围比较小,可以打表过。但是还是用数位dp写写
dp[i][j] : i位数字,j=0表示第i+1位不为6,j=1第i+1位为6
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int s[15];
int dp[15][15];
int swdp(int pos,int status,int limit)//当前位,上一位是否为6,limit当前位是否受限
{
if(pos<=0) return 1;//如果前面的位都满足条件,说明这个数是合法的,就返回1
if(!limit&&dp[pos][status]!=-1) return dp[pos][status];//如果不受限,就直接返回之前记录的,所以dp中放的是不受限的
int a=limit? s[pos]:9;
int ret=0;
for(int i=0;i<=a;i++)
{
if(status&&i==2) continue;
if(i==4) continue;
ret+=swdp(pos-1,i==6,limit&&i==a);
}
if(!limit) dp[pos][status]=ret;//放入不受限的
return ret;
}
int solve(int n)
{
memset(s,0,sizeof(s));
int u=0;
while(n)
{
s[++u]=n%10;
n=n/10;
}
memset(dp,-1,sizeof(dp));
return swdp(u,0,1);
}
int main()
{
int n,m;
//memset(dp,-1,sizeof(dp));这里可以优化
while(scanf("%d %d",&n,&m)!=EOF)
{
if(m==0&&n==0) break;
printf("%d\n",solve(m)-solve(n-1));//当前位算了
}
return 0;
}
优化:第一:memset(dp,-1,sizeof dp);放在多组数据外面。
这一点是一个数位特点,使用的条件是:约束条件是每个数自身的属性,而与输入无关。
第二:相减。
HDU 4734
题意:给你一个A,B,AnAn-1..A1 为数字A的10十进制
F(x)=An∗2n−1+An−1∗2n−2+...+A2∗2+A1∗1
问在[0,B]中,有多少数字x,f(x)<=f(A)
T (T <= 10000) A and B (0 <= A,B < 10^ 9)$
如果我们直接算的话,dp[pos][sum],sum为前缀和。这样的话,sum的值和给我们的数字B有关,因为sum是从B的最高位开始的前缀和。这样的话dp数组在每计算一次的话,就需要初始化了(初始化不能放在外面了)一共有T组数据,这样的话,光初始化就会超时。。
能不能想办法把初始化放在外面呢?有什么东西是不变的呢,是不随B的变化变的,从低位开始的前缀和!,所以dp[pos][fa-sum]就是我们的状态
Round Numbers POJ - 3252
题意:问你【l,r】内有多少个数字的二进制中0的数量不小于1的数量。
1 ≤ l < r ≤ 2,000,000,000).
我们的状态为dp[pos][num]:num为0与1的差,中途会出现负数的。所以我们加一个32.这里是不能有这里是不能有前导零的。
HDU 3709 这题就是要枚举中轴,然后数位dp
CodeForces 55D Beautiful numbers 枚举数位和,然后dp
HDU 6156 Palindrome Function 枚举进制k,然后用统计回文串
http://blog.youkuaiyun.com/wust_zzwh/article/details/52100392 这个讲的很好