问题虫洞: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;
}