hdu3555 Bomb ——数位DP入门题

本文介绍了一种使用数位动态规划解决特定数字模式计数问题的方法,详细解析了状态转移方程及实现技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击跟博主一起玩(zuo)耍(si)
题目大意:

  给一个数字n,范围在1~2^63-1,求1~n之间含有49的数字有多少个。

思路:

  经典的数位DP,学习了一下,看的别人的代码:http://www.cnblogs.com/luyi0619/archive/2011/04/29/2033117.html

  状态转移:

  dp[i][0]代表长度为 i 并且不含有49的数字的个数;

  dp[i][1]代表长度为 i 并且不含有49,但是最高位是9的数字的个数;

  dp[i][2]代表长度为 i 并且含有49的数字的个数。

  数组 a[i] 从低位到高位存储 n 的每一位数字。

  则:dp[i][0] = dp[i-1][0] * a[i] - dp[i-1][1]; 表示长度为 i 的不含有49的数字的个数等于长度为 i - 1 的不含有49的数字的个数*当前的数字,因为这个位置可以填0~a[i] - 1,然后再减去长度为 i - 1 的最高位是9的数字的个数,因为如果长度为 i - 1 的最高位是9的话,那么高一位就不能填4了,否则就组成了49。

    dp[i][1] = dp[i-1][0]; 表示长度为 i 的并且不含有49同时最高位是9的数字的个数等于,长度为 i - 1 的不含有49的数字的个数,因为只要在它的高一位加上一个9就可以了。

    dp[i][2] = dp[i-1][2] * a[i] + dp[i-1][1]; 表示长度为 i 的含有49的数字的个数等于,长度为 i - 1 的数字的个数*当前的数字,再加上长度为 i - 1 的并且不含有49同时最高位是9的数字的个数,因为这个时候,只要在高一位加上一个4就可以了,这样在最高的两位就组成了一个49。

  做法是从数字的高位向低位扫描,对于第 i 位,

  首先加上长度为 i - 1 的符合条件的数字个数;
  再讨论以前是不是出现过49,如果出现过,就要再追加上长度为 i - 1 的不符合条件的数字的个数,因为以前已经有49了;
  如果没有出现过,就要判断这一位是不是大于4呢,如果大于4,就要再追加上长度为 i - 1 的不含有49但是最高位是9的数字的个数,因为这个时候可以再这一位填4,因为它大于4嘛~;
  然后就是判断一下,当前位和上一位是不是满足49,如果满足,标记出现了49了!为以后的判断做准备。

  其实这个题目还有一个地方不懂,就是为什么要在输入 n 后,要把 n 加1。想了一下特例,比如输入49,按照上面的做法,在第3步,并不会把符合条件的数字加上,因为4不是严格大于4,最后的执行结果就是0,但是如果加上1之后,n就变成了50,这样第3步恰好可以执行,结果就是正确的了。但是对于一般的情况,还是不知道为什么要把n加1……o(╯□╰)o

  这题还是卡了很久,照着别人的代码敲的,死活过不了,然后又找了一份代码:http://blog.youkuaiyun.com/acm_cxlove/article/details/7819907 才发现输入输出要用%I64d,这不是坑么……原来hdu要用%I64d,囧……

  所以,有时候的bug不是算法或者代码有错误,看看你的输入输出吧!还有,类似的情况,比如,输入文件写错了……更悲剧了。。
代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <stack>
#include <queue>
#include <cmath>
#include <algorithm>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long int LL;
const int MAXN =  0x3f3f3f3f;
const int  MIN =  -0x3f3f3f3f;
const double eps = 1e-9;
const int dir[8][2] = {{0,1},{1,0},{0,-1},{-1,0},{-1,1},
  {1,1},{1,-1},{-1,-1}};
LL dp[21][3]; unsigned long long int n; int a[25];
int main(void){
#ifndef ONLINE_JUDGE
  freopen("hdu3555.in", "r", stdin);
#endif
  int t; scanf("%d", &t);
  memset(dp, 0, sizeof(dp));
  dp[0][0] = 1;
  for (int i = 1; i < 21; ++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];
  }
  while (t--){
    //cin >> n;
    scanf("%I64d", &n);
    int len = 0; memset(a, 0, sizeof(a));
    n++;
    while (n){
      a[++len] = n % 10; n /= 10;
    } LL ans = 0; int last = 0; bool flag = false;
    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 (last == 4 && a[i] == 9) {flag = true;}
      last = a[i];
    }
    //cout << ans << endl;
    printf("%I64d\n", ans);
  }

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值