問題蟲洞: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]表示首位是1的6位數,即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;
}