1. 程式人生 > >杭電 hdu 2089 不要62【數位dp】【入門】

杭電 hdu 2089 不要62【數位dp】【入門】

不要62

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 29117    Accepted Submission(s): 10217


Problem Description 杭州人稱那些傻乎乎粘嗒嗒的人為62(音:laoer)。
杭州交通管理局經常會擴充一些的士車牌照,新近出來一個好訊息,以後上牌照,不再含有不吉利的數字了,這樣一來,就可以消除個別的士司機和乘客的心理障礙,更安全地服務大眾。
不吉利的數字為所有含有4或62的號碼。例如:
62315 73418 88914
都屬於不吉利號碼。但是,61152雖然含有6和2,但不是62連號,所以不屬於不吉利數字之列。
你的任務是,對於每次給出的一個牌照區間號,推斷出交管局今次又要實際上給多少輛新的士車上牌照了。

Input 輸入的都是整數對n、m(0<n≤m<1000000),如果遇到都是0的整數對,則輸入結束。

Output 對於每個整數對,輸出一個不含有不吉利數字的統計個數,該數值佔一行位置。

Sample Input 1 100 0 0
Sample Output 80
Author qianneng
Source

這個題目是在以前做過的,那個時候用暴力就能過,也從未想過用dp什麼的,畢竟年輕、、、然後今天回來補補知識~也給大家分享一下自己的理解~

我們首先要對dp【】【】陣列進行確定含義和預處理、這裡我們規定dp【i】【j】表示的是以j開頭的i位數的符合條件的個數、我們這裡說的可能不是很容易理解,我們這裡舉個栗子:dp【2】【6】=8、表示從60~69中滿足條件的個數、60、61、63、65、66、67、68、69(8個~)

我們這裡再舉個栗子dp【3】【0】:表示從1~100中滿足條件的個數、(這裡就不枚舉了、、、)【0,99】

那麼dp【3】【1】呢?表示從100~200中滿足條件的個數、【100,199】

這裡我們知道了dp陣列的含義,那麼我們就來進行預處理吧:這裡我們順便貼上dp陣列內資料,供參考

1 1 1 1 0 1 1 1 1
9 9 9 9 0 9 8 9 9
80 80 80 80 0 80 71 80 80
711 711 711 711 0 711 631 711 711
6319 6319 6319 6319 0 6319 5608 6319 6319
56160 56160 56160 56160 0 56160 49841 56160 56160
499121 499121 499121 499121 0 499121 442961 499121 499121

初始化dp程式碼://對於這部分程式碼、仔細看、認真看、領悟領悟就懂了~

    dp[0][0]=1;<span style="white-space:pre">																		</span>      //十位dp加個位dp、百位dp加十位dp(從而就加上了個位dp)<span style="white-space:pre">	</span>
    for(int i=1;i<=7;i++)
    {
        for(int j=0;j<10;j++)//列舉第i位數上的數字、
        {
            for(int k=0;k<10;k++)//列舉第i-1位上的數字、
            {
                if(!(j==6&&k==2)&&j!=4)//滿足條件
                dp[i][j]+=dp[i-1][k];
            }
        }
    }
這裡我們知道了整塊整塊的資料之後呢(比如dp【2】【6】我們知道了是什麼含義,但是我們要對整塊整塊的資料加和才能得到最終結果)就要對小細節進行處理了~有了以上的資料之後呢,我們知道,處理當前這個數之前,需要兩個元素:

1、各個位上的資料

2、資料的長度

這裡我們很容易就能用函式來實現:

int calchangdu(int n)//長度
{
    int cont=0;
    while(n)
    {
        cont++;
        n/=10;
    }
    return cont;
}
int caldigit(int n,int len)//各個位上的資料
{
    memset(digit,0,sizeof(digit));
    for(int i=1;i<=len;i++)
    {
        digit[i]=n%10;
        n/=10;
    }
}
有了這麼些個已知條件之後、我們只要對資料逐一判斷處理就行了~

我們這裡求得【0,n)的滿足條件的個數的方法如下:

int solve(int n)//計算[0,n)符合條件的個數//※這個函式是最主幹的部分
{
     int ans=0;
     int len=calchangdu(n);
     caldigit(n,len);
     for(int i=len;i>=1;i--)//從最高位開始列舉
     {
         for(int j=0;j<digit[i];j++)//列舉第i位包含的資料
         {
             if(!(j==2&&digit[i+1]==6)&&j!=4)//當然要滿足條件才能加、
             {
                 ans+=dp[i][j];
             }
         }
        if(digit[i]==4 || (digit[i]==2 && digit[i+1]==6))//第i位已經不滿足條件,則i位以後都不可能滿足條件,結束迴圈
            break ;
     }
     return ans;
}
所有內容都確定好了之後,那麼求【n,m】的方法也很直接了:

求【0,m】的個數,然後求【0,n-1】的個數,然後相減,就得到了最終得數。我們這裡上完整的AC程式碼:

#include<stdio.h>
#include<string.h>
using namespace std;
int dp[10][10];
int digit[10];
void init()
{
    //十位加個位dp,百位加十位dp,千位加百位dp
    dp[0][0]=1;
    for(int i=1;i<=7;i++)
    {
        for(int j=0;j<10;j++)//列舉第i位數上的數字、
        {
            for(int k=0;k<10;k++)//列舉第i-1位上的數字、
            {
                if(!(j==6&&k==2)&&j!=4)//滿足條件
                dp[i][j]+=dp[i-1][k];
            }
        }
    }
}
int calchangdu(int n)
{
    int cont=0;
    while(n)
    {
        cont++;
        n/=10;
    }
    return cont;
}
int caldigit(int n,int len)
{
    memset(digit,0,sizeof(digit));
    for(int i=1;i<=len;i++)
    {
        digit[i]=n%10;
        n/=10;
    }
}
int solve(int n)//計算[0,n)符合條件的個數
{
     int ans=0;
     int len=calchangdu(n);
     caldigit(n,len);
     for(int i=len;i>=1;i--)//從最高位開始列舉
     {
         for(int j=0;j<digit[i];j++)
         {
             if(!(j==2&&digit[i+1]==6)&&j!=4)
             {
                 ans+=dp[i][j];
             }
         }
        if(digit[i]==4 || (digit[i]==2 && digit[i+1]==6))//第i位已經不滿足條件,則i位以後都不可能滿足條件,結束迴圈
            break ;
     }
     return ans;
}
int main()
{
    init();
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        printf("%d\n",solve(m+1)-solve(n));
    }
}