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;
}

 

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