hdu3555——Bomb[數位dp打表法]

問題蟲洞:Bomb

 

黑洞內窺:

給出一個數n,求1~n內不含49的數有多少個

1<= n <= 2^63-1;

 

思維光年:

由於數據非常的大,暴力會T!~~~

所以我去學了一種新算法——數位dp,

數位dp:

數位DP”對數字的進行的與計數有關的DP

一個數有個位、十位、百位、千位......數的每一位就是數位。

數位DP用來解決與數字操作有關的問題,例如數位之和問題、特定數字問題等等。

這些問題的特徵是:給定的區間超級大,不能用暴力的方法逐個檢查,必須用接近O(logn)複雜度的算法。解題的思路,是用DP數位進行操作,記錄已經算過的區間的狀態,用在後續計算中,快速進行大範圍的篩選。

和所有的dp一樣,一開始都是區間打表:

定義狀態dp[i][j]它表示i位數中,首位是j,符合要求的數的個數。

例如dp[6][1]表示首位是16位數,即100000~199999中符合要求的數有多少個。

可以得出遞推公式:

其中(j != 4 && k != 9)

所以,我的未AC代碼爲:

#include <bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<map>
#include<algorithm>
#include<cstring>
#include<string.h>
#include<math.h>
using namespace std ;
typedef long long ll;
#define MAXN 100005
#define INF 0x3f3f3f3f//將近int類型最大數的一半,而且乘2不會爆int
const int LEN = 20;  //可以更大
__int64 dp[LEN][10];     //dp[i][j]表示i位數,第一個數是j時,符合條件的數字數量
int digit[LEN+1];      //digit[i]存第i位數字

void init()
{
    memset(dp, 0, sizeof(dp));
    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++)
                dp[i][j] += dp[i-1][k];
            if(j == 4)
                dp[i][j]-=dp[i-1][9];
        }
}

__int64 solve(int len)                //計算[0,n]區間滿足條件的數字個數
{

    __int64 ans = 0;
    for(int i=len; i>=1; i--)     //從高位到低位處理
    {
        for(int j=0; j<digit[i]; j++)
            if(!(digit[i+1]==4&&j==9))
                ans+=dp[i][j];
        if(digit[i+1] == 4 && digit[i] == 9)
            break;
    }
    return ans;
}

int main()
{
    init();                          //預計算dp[][]
    int t;
    cin >> t;
    while(t--)
    {
        memset(digit, 0, sizeof(digit));
        __int64 n;
        scanf("%I64d",&n);
        __int64 tem = n;
        int len = 0;
        while(n)
        {
            digit[++len] = n%10;
            n/=10;
        }
        digit[len+1] = 0;
        __int64 cnt = tem-solve(len);
        printf("%I64d\n", cnt);
    }
    return 0;
}

但是可以AC的代碼爲:

#include <bits/stdc++.h>
using namespace std;
 
__int64 dp[21][10];
 
void init(void){
	memset(dp,0,sizeof(dp));
	dp[0][0] = 1;
	int i,j,k;
	for(i=1 ;i<21 ;i++){
		for(j=0 ;j<10 ;j++){
			for(k=0 ;k<10 ;k++){
				if(!(j==4 && k==9))
					dp[i][j] += dp[i-1][k];
			}
		}
	}
}
 
__int64 solve(__int64 x){
	__int64 tem = x;
	int len = 0,digit[21];
	while(x){
		digit[++len] = x % 10;
		x /= 10;
	}
	digit[len+1] = 0;
	int i,j;
	__int64 ans = 0;
	for(i=len ;i>0 ;i--){
		for(j=0 ;j<digit[i] ;j++){
			if(!(digit[i+1] == 4 && j == 9)){
				ans += dp[i][j];
			}
		}
		if(digit[i+1] == 4 && digit[i] == 9)
			break;
	}
	return tem - ans;
}
 
int main(){
	init();
	int T;
	scanf("%d",&T);
	while(T--){
		__int64 n;
		scanf("%I64d",&n);
		printf("%I64d\n",solve(n+1));
	}
	return 0;
}

 

不明白什麼原理,我爲什麼就一直wa,下面的代碼就可以AC,頭疼了一晚上呢~~~~

說好的1024沒有bug呢?一天卡一個bug,一個bug卡一天~~~~~~~~

 

十分鐘後,,,,,

我回來了。。。。。n要加1,加1就過了。。。。我哭了~~~~~~1024萬歲!1024萬歲!!1024萬歲!!!

(其實,開long long 就可以過。。。。)

#include <bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<map>
#include<algorithm>
#include<cstring>
#include<string.h>
#include<math.h>
using namespace std ;
typedef long long ll;
#define MAXN 100005
#define INF 0x3f3f3f3f//將近int類型最大數的一半,而且乘2不會爆int
const int LEN = 20;  //可以更大
__int64 dp[LEN][10];     //dp[i][j]表示i位數,第一個數是j時,符合條件的數字數量
int digit[LEN+1];      //digit[i]存第i位數字

void init()
{
    memset(dp, 0, sizeof(dp));
    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++)
                dp[i][j] += dp[i-1][k];
            if(j == 4)
                dp[i][j]-=dp[i-1][9];
        }
}

__int64 solve(int len)                //計算[0,n]區間滿足條件的數字個數
{

    __int64 ans = 0;
    for(int i=len; i>=1; i--)     //從高位到低位處理
    {
        for(int j=0; j<digit[i]; j++)
            if(!(digit[i+1]==4&&j==9))
                ans+=dp[i][j];
        if(digit[i+1] == 4 && digit[i] == 9)
            break;
//        如果n不加1,則在結束時要--;
//         if(digit[i+1] == 4 && digit[i] == 9)
//        {
//            ans--;
//            break;
//        }
    }
    return ans;
}

int main()
{
    init();                          //預計算dp[][]
    int t;
    cin >> t;
    while(t--)
    {
        memset(digit, 0, sizeof(digit));
        __int64 n;
        scanf("%I64d",&n);
        n++;                ///主要的加1, 我哭了~~~
        __int64 tem = n;
        int len = 0;
        while(n)
        {
            digit[++len] = n%10;
            n/=10;
        }
        digit[len+1] = 0;
        __int64 cnt = tem-solve(len);
        printf("%I64d\n", cnt);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章