HDU 2089 不要62(数位DP入门)

    执着追求并从中得到最大快乐的人,才是成功者。——梭罗
    第一次做这个题是暴力过的,感觉也没啥,今天集训讲数位DP,想用数位DP做一下。自己还真是菜的扣脚啊~ 写一下题解如果有错误希望指正!不胜感激。

原题链接

    f[i][j] i代表有几位数,j代表起始数字
    例如f[3][1] 代表的是 100~199
    即使是2^63-1 用 f[18][9]表示也足够了 还真是神奇!
    先找到最优解:![这里写图片描述](https://img-blog.csdn.net/20160805224218669)
    既然找到最优解那就好办了,至于为什么得到上面的式子呢?
        1.当j=4也就是i位数4开头,当然是0咯
        2.当不是6 2时可以通过一个例子来找到答案
            假设f[3][3],即3XX
            3XX可以写成30X、31X、32X......39X
            是不是发现规律了呢?
            因为最高位3是一定的,所以我们只需要把f[i-1][k](0<=k<=9)加起来就行了

    此时f[i][j]中存放了许多数据,让我们看一下:
![数据](https://img-blog.csdn.net/20160805225937693)


    既然处理好了f[i][j],接下来就是处理输出问题了(这里是我整理了一下其他人的博客,再加上上面的一些解释,应该没问题了)
    如何根据数组f求对于一个数n,0~n满足要求的数呢?
    例如335,对应f[3][3],但是满足要求的数有336,347,358.....这些数超过了335
    所以通过f[3][0],f[3][1],f[3][2]求得首位数字小于3的满足条件的三位数,f[3][0]求得的数是001,052,093...也就是所有满足条件的一位或两位数……我们求得所有的2xx,1xx,0xx.
    接下来要求的是如334,327这类首位是3满足条件的数字,既然首位只能是3,也就是第一位已经选完了,那么我们只要选择满足小于35的数字就可以了。和上面同理,我们只要求出f[2][0],f[2][1],f[2][2],就可以求出首位小于3的两位数。这时求得的是32x,31x,30x。
    然后我们求所有小于5的数字就ok了。小于5也就是f[1][0],f[1][1],f[1][2],f[1][3],f[1][4]。这时求得的是33x.
    到此,所有小于335且满足的数全部求出。
    但是还需要考虑的问题是62,4。
    如果某一位数字为4,如77432的第三位(从左向右看)是4,那么我们依次求出 
    1、6xxxx,5xxxx,...,0xxxx  
    2、76xxx,75xxx,...,70xxx
    3、773xx,773xx,...,770xx
    我们接来下需要求的是7742x....7740x,但这时所求的数是774xx的数,必然含有4,这时停止。
    对于62,如果某一位数字是6,如5675,我们求完5xxx,4xxx,...0xxx之后再求56xx时,注意566x,565x,..,563x,561x,没有562x。如果数字中含有62,如5627,那么同4,求到562x的时候就应该停止继续求下去了。
    因为求得的是小于n的数,所以求[0,n]之间的数需要求n+1.

以下是代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;

int f[12][10];
int num[12];
void init(){
    memset(f,0,sizeof(f));
    int i,j,k;
    f[0][0]=1; 
    for(i=1; i<=7; i++)
        for(j=0; j<10; j++)
            for(k=0; k<10; k++)
                if(j!=4 &&!(j==6 &&k==2))
                    f[i][j]+=f[i-1][k]; 
}

int solve(int n){
    int len=1;
    memset(num,0,sizeof(num));
    while(n){
        num[len++]=n%10;
        n/=10;
    }

    int ans=0; 
    for(int i=len-1; i ; i--){ 
        for(int j=0; j<num[i]; j++){
            if(j!=4 &&!(num[i+1]==6 && j==2))
                ans+=f[i][j];
        }
        if(num[i]==4 || ( num[i]==2 && num[i+1]==6 ))
            break;
    }
    return ans;
}
int main(){
    init();
    int S,E;
    while(scanf("%d%d",&S,&E)!=EOF && S+E){
        cout<<solve(E+1)-solve(S)<<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值