[题解]HDU 3555 Bomb

啊哈是从CSDN搬过来的啦——(日历上没有下划线不爽)

传呀传呀传送门
好久没有写题解了的说QAQ

原题

The counter-terrorists found a time bomb in the dust. But this time the terrorists improve on the time bomb. The number sequence of the time bomb counts from 1 to N. If the current number sequence includes the sub-sequence "49", the power of the blast would add one point.
Now the counter-terrorist knows the number N. They want to know the final points of the power. Can you help them?

Input

The first line of input consists of an integer T (1 <= T <= 10000), indicating the number of test cases. For each test case, there will be an integer N (1 <= N <= 2^63-1) as the description.
The input terminates by end of file marker.

Output

For each test case, output an integer indicating the final points of the power.

Sample Input

3
1
50
500

Sample Output

0
1
15

Hint

From 1 to 500, the numbers that include the sub-sequence "49" are "49","149","249","349","449","490","491","492","493","494","495","496","497","498","499",
so the answer is 15.

题目描述

求小于等于n的数中包含49的数有多少

分析

首先肯定是想到DP:
设f[i]表示长度为i的数中有多少包含"49"
在这里插入图片描述
比方说n=56789时
在这里插入图片描述
假设我们要找的数可以表示为\(\bar{a_5a_4a_3a_2a_1}\)也就是说等于\(a_5*10000+a_4*1000+a_3*100+a_2*10+a_1\),我们考虑来枚举这个数——
因为它一定比n小,所以\(a_5\leq 5\)

  • \(a_5<5\)时,也就是\(a_5\)\(0,1,2,3,4\)五种取法的时候,后面的\(a_4 a_3...\)不管怎么放都不会大于n,所以这时答案就可以加上\(f[4]*5\)
  • \(a_5==5\)时,继续枚举后面的数,按照相同的方式去看这两个数从那一位开始不同就可以了

这样看上去仿佛很对
在这里插入图片描述
但是我们发现它会比正确答案小,这是为什么呢?
在这里插入图片描述
我们发现,当\(a_5\)取4时,\(\bar{a_4a_3a_2a_1}\)\(900\)居然也是合法的,所以我们还要考虑当现在放了\(4\)的情况,当放了4时,我们需要再加上以9开头的数的数量
所以我们改变一下数组

  • \(f[i][1]\)表示长度为i,以9开头的数的数量
  • \(f[i][2]\)表示长度为i,包含"49"的数的数量

那么,当我们考虑到\(a_5\)时,要讨论3中情况

  • \(a_5<5\)\(a_5\neq4\)时,也就是\(a_5\)\(0,1,2,3\) 四种取法的时候,后面的\(a_4 a_3...\) 不管怎么放都不会大于n,所以这时答案就可以加上\(f[4][2]*4\)
  • \(a_5==4\)时,要加上\(f[4][2]\)再加上以9开头的数量\(f[4][1]\),但是我们发现,像\(9049\)这些既以9开头又包含"49"的数会被计算到两次,那么还要减去\(f[3][2]\)
  • \(a_5==5\)时,和上面的相同

然而问题又来了——
输入494949发现又一次GG了TAT
我们考虑n中本身就包含"49"

在这里插入图片描述
那么当我们考虑\(a_3\)时,前面已经包含49了,就不需要再讨论后面有没有包含49,所以下面就是正解了——
先预处理出三个数组

  • \(f[i][0]\)表示长度为i的数有几个
  • \(f[i][1]\)表示长度为i,不包含"49"的数有几个
  • \(f[i][2]\)表示长度为i,包含"49"的数有几个

假设原数n为\(\bar{b_5b_4b_3b_2b_1}\)从高位向低位扫

  • 如果前面的序列中已经包含"\(49\)",那么\(a_i<b_i\),后面的数都可以随便取,对答案的贡献为\(f[i-1][0]*b[i]\)
  • 如果当前\(b_i\leq4\),那么 对于\(a_i<b_i\),后面的数都可以随便取
  • 如果当前\(b_i>4\),那么 对于\(a_i<b_i\),后面也是可以随便取,还有特殊情况\(a_i=4\),后面还可以取以\(9\)开头的没有被计算过的数,\(ans+=b[i]*f[i-1][2]+f[i-1][1]\)

最后要注意细节

  • 比如说当前面包含49时最终答案要加1,因为最后一个数没有被算到
    就是比如n=498时498没有算
  • 再比如说多组数据没有清零就挂了n发

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <string>
#include <vector>
#define ll long long
const int N=100005;
const int M=200005;
using namespace std;

ll n,yh;
int len,flag=0,a[N];
ll f[N][3],ans=0;

int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		ans=len=flag=0;yh=1;
		scanf("%lld",&n);
		for(int i=1;i<=100;i++){
			yh*=10;
			if(yh>n){
				len=i;
				break;
			}
		}
		f[0][0]=1;
		for(int i=1;i<=len;i++){
			f[i][0]=f[i-1][0]*10;
			f[i][1]=f[i-1][0]-f[i-1][2];
			if(i>=2)f[i][2]=f[i-1][2]*10+f[i-1][1];
		}
		for(int i=1;i<=len;i++){
			a[i]=n%10;
			n/=10;
		}
		for(int i=len;i>=1;i--){
			if(flag){
				ans+=a[i]*f[i-1][0];
				continue;
			}
			if(a[i]<=4){
				ans+=a[i]*f[i-1][2];
			}
			else{
				ans+=(a[i]-1)*f[i-1][2];
				ans+=f[i-1][2]+f[i-1][1];
			}
			if(a[i]==9&&a[i+1]==4)flag=1;
		}
		if(flag)ans++;
		printf("%lld\n",ans);
		for(int i=1;i<=len;i++)f[i][0]=f[i][1]=f[i][2]=a[i]=0;
	}
	return 0;
}
posted @ 2019-11-16 20:22  硫氯  阅读(106)  评论(0编辑  收藏  举报