题目
给你一个数n,问[1,n]包括49的数有多少个,n<(1<<63)
思路来源
https://blog.csdn.net/u012860063/article/details/46820639
https://www.2cto.com/kf/201409/338701.html
题解
这题分前面出现49和后面出现49讨论……
都在代码的注释里了……
dp[i][0] i位数 不包含49的方案数
dp[i][1] i位数 不包含49且以9开头的方案数
dp[i][2] i位数 包含49的方案数
统计时维护是不是已经出现49和这一位填几
感觉数位dp统计答案的关键是,这一位填几QAQ
心得
统计完比这一位小的数开头的数的贡献之后,这一位就定下来了
模拟写数的过程,才有以下的三个经典操作,
枚举的i是数的位数,才会用a[0]来去记有多少位,
考虑在转移的过程中,需要什么状态才能继续往下转移,从而达到目的
数位dp还是很套路的,分三个步骤
①确定表达式,一般是dp[i][j]表示i位数最高位是j的方案数,但像本题就不一样
②预处理,考虑数字与数字之间的转移关系
③统计,从高到低枚举每一位a[i],统计[0,a[i]-1]对这一位的贡献,当前位一定能取[0,a[i]-1]
数位dp经典操作
①dp[0][0]=1,初值赋1
②a[a[0]+1]=0,高位赋0
③[0,x),solve(x+1),询问加1
代码1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
const int maxn=1e6+10;
typedef unsigned long long ull;
using namespace std;
//dp[i][0] i位数 不包含49
//dp[i][1] i位数 以9开头 不包含49
//dp[i][2] i位数 包含49
int t;
ull dp[20][3],a[20];
void init()
{
dp[0][0]=1;
for(int i=1;i<=18;++i)
{
for(int j=0;j<=9;++j)
{
dp[i][0]=dp[i-1][0]*10-dp[i-1][1];//dp[i-1][0]前填0-9 减去其中以9开头前填4的情况
dp[i][1]=dp[i-1][0];//dp[i-1][0]前填9即可
dp[i][2]=dp[i-1][2]*10+dp[i-1][1];//dp[i-1][2]已经包含49前填0-9 加上以9开头前填4的情况
}
}
}
ull cal(ull x)
{
ull ans=0;
a[0]=0;
while(x)
{
a[++a[0]]=x%10;
x/=10;
}
a[a[0]+1]=0;//保证a[0]的上一位不为4
bool flag=0;//是否已出现49
for(int i=a[0];i>=1;--i)//从高到低统计
{
ans+=dp[i-1][2]*a[i];//这位可以填0-(a[i]-1)随便填 反正后面有49
if(flag)ans+=dp[i-1][0]*a[i];//这一位随便填 反正前有49
else if(a[i]>4)ans+=dp[i-1][1];//这一位可以填4 和后面的9开头的凑出49
if(a[i+1]==4&&a[i]==9)flag=1;//出现49了
}
return ans;
}
int main()
{
init();
scanf("%d",&t);
while(t--)
{
ull x;
scanf("%llu",&x);
printf("%llu\n",cal(x+1));
}
return 0;
}
代码2(自己瞎搞了一个也过了)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
const int maxn=1e6+10;
typedef unsigned long long ull;
using namespace std;
//dp[i][j] j为最高的i位数的答案
//f[i] 后面缀几个0
int t;
ull dp[20][10],f[20],a[20];
void init()
{
dp[2][4]=1;f[0]=1;
for(int i=1;i<=18;++i)f[i]=f[i-1]*10;
for(int i=3;i<=18;++i)
{
for(int j=0;j<=9;++j)//49 149 249 349 449 490-499
{
for(int k=0;k<=9;++k)
{
if(j==4&&k==9)dp[i][j]+=f[i-2];
else dp[i][j]+=dp[i-1][k];
}
//高位4 次高位9 剩下随便取
//4900-4999会将4949重复计算 故不加 单独计算
//printf("%lld到%lld共有%lld个\n",j*f[i-1],(j+1)*f[i-1]-1,dp[i][j]);
}
}
}
ull cal(ull x)
{
ull ans=0;
for(int i=0;i<=19;++i)a[i]=0;
while(x)
{
a[++a[0]]=x%10;
x/=10;
}
for(int i=a[0];i>=1;--i)//从高到低统计
{
for(int j=0;j<a[i];j++)
{
ans+=dp[i][j];
}
if(a[i+1]==4&&a[i]==9)//后面的会受到前缀49的影响 直接一并统计然后跳出来
{
ull res=0;
for(int j=i-1;j>=1;j--)
res=res*10+a[j];
ans+=res;
break;
}
}
return ans;
}
int main()
{
init();
scanf("%d",&t);
while(t--)
{
ull x;
scanf("%llu",&x);
printf("%llu\n",cal(x+1));
}
return 0;
}
代码3(后记:20190504)
自己又瞎搞了一个划分
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
int t;
ull n;
ull dp[20][3];//dp[i][j]表示i位数的状态
//dp[i][0]:i位数不含49且最高位不是9
//dp[i][1]:i位数不含49最高位是9
//dp[i][2]:i位数含49
ull a[20];
void init()
{
dp[0][0]=1;//视具体情况而定
//dp[1][0]到dp[1][9]都得从dp[0][0]里取状态 故赋1
for(int i=1;i<=19;++i)
{
dp[i][0]=dp[i-1][0]*9+dp[i-1][1]*8;
dp[i][1]=dp[i-1][0]+dp[i-1][1];
dp[i][2]=10*dp[i-1][2]+dp[i-1][1];
}
}
ull solve(ull x)
{
ull ans=a[0]=0;
for(;x;x/=10)
a[++a[0]]=x%10;
a[++a[0]]=0;//高位补0
bool flag=0;//是否已经出现49
for(int i=a[0]-1;i>=1;--i)//i是几位数
{
ans+=a[i]*dp[i-1][2];
if(flag)ans+=a[i]*(dp[i-1][0]+dp[i-1][1]);//[0,a[i]-1]
else if(a[i]>4)ans+=dp[i-1][1];
if(a[i+1]==4&&a[i]==9)flag=1;
}
return ans;
}
int main()
{
init();
scanf("%d",&t);
while(t--)
{
scanf("%llu",&n);
printf("%llu\n",solve(n+1));
}
return 0;
}