分析:
题意为给除一个数N,求出1-N这些数中含有49的子串的数的个数。初学数位DP,这道题就当例题了。题解:
- 首先我们确定状态转移方程
- dp[i][j]用于存储符合条件的数的个数
- i 代表目前处理的数长度为i
- 这里dp[i][j]处理的数可以包含前导0
- dp[i][0]代表长度为i的数中不含49的数量
- dp[i][1]代表长度为i的数中不含49但是最高位为9的数的数量
- dp[i][2]代表长度为i的数中包含49的数的数量
- 代码:
- 首先我们确定状态转移方程
dp[0][0] = 1;
for(int i=1; i<=20; i++)
{
dp[i][0] = dp[i-1][0]*10-dp[i-1][1];
dp[i][1] = dp[i-1][0];
dp[i][2] = dp[i-2][2]*10+dp[i-1][1];
}
处理:
- 对于一个数字n,我们假设它的长度为len,并且第i位储存在a[i]中。
- 首先加上a[i]*剩下i-1位中满足条件的数的个数。
- 如果高位已经出现过49,加上a[i]*剩下i-1位中不满足条件的数的个数。
- 如果高位未出现49,且第i位>4,第i-1位为9,则加上剩下i-1位中以9开头的数的个数。
- 判断高一位和本位是否构成49,更新标志。
代码:
for(int i=len;i>=1;i--)
{
ans+=dp[i-1][2]*a[i];
if(flag)
ans+=dp[i-1][0]*a[i];
if(!flag&&a[i]>4)
ans+=dp[i-1][1];
if(a[i+1]==4&&a[i]==9)
flag=true;
}
注意:
- 我们在输入n后,需要对n进行+1的操作,因为我们上述操作是统计[1,n)区间内符合条件的数的个数,并没有统计n的情况,n+1是为了不再特殊处理n
- 我们在获取n的每一位数字时,应该预先多处理一位,令a[len+1]=0,这样做是为了不特殊处理n<10的情况
AC代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define __int64 long long
__int64 dp[25][3];
void Init()
{
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
int i;
for(i = 1;i<=22;i++)
{
dp[i][0] = dp[i-1][0]*10-dp[i-1][1];
dp[i][1] = dp[i-1][0];
dp[i][2] = dp[i-1][2]*10+dp[i-1][1];
}
}
__int64 solve(__int64 n)
{
__int64 i,len = 0,a[25],flag = 0,ans = 0;
while(n)
{
a[++len] = n%10;
n/=10;
}
a[len+1] = 0;
for(i = len;i;i--)
{
ans+=dp[i-1][2]*a[i];
if(flag)
ans+=dp[i-1][0]*a[i];
if(!flag && a[i]>4)
ans+=dp[i-1][1];
if(a[i+1] == 4 && a[i] == 9)
flag = 1;
}
return ans;
}
int main()
{
int t;
__int64 n;
scanf("%d",&t);
Init();
while(t--)
{
scanf("%I64d",&n);
printf("%I64d\n",solve(n+1));
}
return 0;
}