数位DP-HDU-3555-Bomb

Bomb

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 11379 Accepted Submission(s): 4044

Problem Description
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.

Author
fatboy_cw@WHU

Source
2010 ACM-ICPC Multi-University Training Contest(12)——Host by WHU

题意是给一个数N,要求1到N中,数位中出现连续的49的数有多少个,比如49,149,249,499之类的。

初次接触数位DP,真的是头都大了,一开始自己瞎想,弄个dp[size][start]来存第size位是start的情况下满足题意的数的个数,最后由于不好处理,没法输出。
后来只好又去查了查,看了些博客,总算把这道题看懂,大致还是分情况讨论了,其实和DFS差不多的感觉,不过看见有个人用DFA做这道题,有点不明觉厉。
先将N按数位存在num[]数组中。
用dp[i][0]存储0~99..9(i个9)中不满足题意的数的个数。
用dp[i][1]存储同上的范围中不满足题意的以9作为第i位数的个数。
用dp[i][2]存储同上的范围中满足题意的数的个数。
当求dp[i][0]时,由于第i位可以取0~9,同时后i-1位必须不满足,所以先加上十个dp[i-1][0],而后,由于第i位取4,第i-1位取9的时候,不满足,所以还要减去dp[i-1][1]。
那么dp[i][0]=dp[i-1][0]*10-dp[i-1][1]。
当求dp[i][1]时,第i位可以取9,那么只要在后i-1位的不满足题意的数前加一个9即可。
即dp[i][1]=dp[i-1][0]。
当求dp[i][2]时,第i位可以取0~9,那么如果后i-1位已经满足条件,随便取第i位都可以,那么先加上10个dp[i-1][2],同时如果第i位取4,第i-1位取9,那么将新增dp[i-1][1]个满足题意的数,加上。
得到dp[i][2]=10*dp[i-1][2]+dp[i-1][1]。
至此可以预处理出题中要求范围的所有dp[i][0~2]。

那么对于一个数N,又要怎样处理呢。
首先我们已经把N存在了num[]数组中,并且能够得到其长度size,令最终输出为sum,初始值为0。
处理方式是从最高位向最低位累加,加到第i位时,前size-i位都取了最大的值作为当前的参考。
如果当前已经处理到第i位(i>=1),那么如果后i-1位是符合的,第i位随便取也能符合(当然不能超过num[i]),那么先加上dp[i-1][2] x num[i]。同时,如果在第i位之前就已经在num[]中出现过连续49,那么这后i位随便取都能符合题意,由于已经加了num[i]个dp[i-1][2],所以还要加上num[i]个dp[i-1][0](由数组定义可知dp[i-1][0]+dp[i-1][2]是一定等于99..9(i-1个)的)。如果第i位之前没有出现过49,那么如果num[i]>4,当第i位取4,第i-1位取9时,又能获得dp[i-1][1]个符合题意的数,所以再加上dp[i-1][1](当然,如果已经出现了49,就不要重复加了)。
一直处理到第1位,此时如果前面出现过49,就加一(因为是没有考虑其本身的)。
输出sum即可。

在做了HDU2089之后,我又用我上述的dp[size][start]的方法做了一次这道题,时间降到15ms。
附上2089方法详解:http://blog.csdn.net/roy_yuan/article/details/49500063


原方法

//
//  main.cpp
//  数位DP-D-Bomb
//
//  Created by 袁子涵 on 15/10/29.
//  Copyright © 2015年 袁子涵. All rights reserved.
//
//  62ms    1604KB

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h>

using namespace std;

int T;
long long int dp[20][3],N;
int num[20];

void handle()
{
    dp[0][0]=1;
    for (int i=1; i<=18; 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];
    }
}

long long int DP()
{
    int size=0;
    long long int temp=N;
    memset(num, 0, sizeof(num));
    while (temp) {
        num[++size]=temp%10;
        temp/=10;
    }
    bool flag=0;
    for (int i=size; i>0; i--) {
        temp+=dp[i-1][2]*num[i];
        if (flag)
            temp+=dp[i-1][0]*num[i];
        else if (num[i]>4)
            temp+=dp[i-1][1];
        if (num[i+1]==4 && num[i]==9)
            flag=1;
    }
    if (flag)
        temp++;
    return temp;
}

int main(int argc, const char * argv[]) {
    cin >> T;
    memset(dp, 0, sizeof(dp));
    handle();
    while (T--) {
        cin >> N;
        long long int out=0;
        out=DP();
        cout << out << endl;
    }
    return 0;
}

最新

//
//  main.cpp
//  数位DP-D-Bomb-New
//
//  Created by 袁子涵 on 15/10/30.
//  Copyright © 2015年 袁子涵. All rights reserved.
//
//  15ms    1596KB

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <math.h>
#include <iostream>

using namespace std;

int T;
long long int N,dp[20][10];

void handle()
{
    for (int i=0; i<10; i++)
        dp[1][i]=0;
    for (int i=2; i<=18; i++) {
        for (int j=0; j<=9; j++) {
            dp[i][j]=0;
            if (j==4) {
                for (int k=0; k<=9; k++) {
                    if (k==9)
                        dp[i][j]+=(long long int )pow(10, i-2);
                    else
                        dp[i][j]+=dp[i-1][k];
                }
                continue;
            }
            else
                for (int k=0; k<=9; k++) {
                    dp[i][j]+=dp[i-1][k];
                }
        }
    }
}

long long int DP(long long int N)
{
    char num[20];
    memset(num, 0, sizeof(num));
    int size=0;
    long long int sum=0;
    while (N) {
        num[++size]=N%10;
        N/=10;
    }
    bool flag=0;
    for (int i=size; i>=1; i--) {
        if (flag)
        {
            sum+=num[i]*pow(10, i-1);
            continue;
        }
        for (int j=num[i]-1; j>=0; j--)
            sum+=dp[i][j];
        if (num[i]==9 && num[i+1]==4)
            flag=1;
    }
    return sum;
}

int main()
{
    memset(dp, 0, sizeof(dp));
    memset(dp, 0, sizeof(dp));
    handle();
    cin >> T;
    while (T--) {
        scanf("%lld",&N);
        N=DP(N+1);
        printf("%lld\n",N);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值