FZU_2019_Mountain Number题解

我滴头啊~晕啊晕。这题终于让我把数位DP的本质看清楚了。

http://acm.fzu.edu.cn/problem.php?pid=2109

题目
Problem Description



One integer number x is called "Mountain Number" if:



(1) x>0 and x is an integer;



(2) Assume x=a[0]a[1]...a[len-2]a[len-1](0≤a[i]≤9, a[0] is positive). Any a[2i+1] is larger or equal to a[2i] and a[2i+2](if exists).



For example, 111, 132, 893, 7 are "Mountain Number" while 123, 10, 76889 are not "Mountain Number".



Now you are given L and R, how many "Mountain Number" can be found between L and R (inclusive) ?



 Input



The first line of the input contains an integer T (T≤100), indicating the number of test cases.



Then T cases, for any case, only two integers L and R (1≤L≤R≤1,000,000,000).



 Output



For each test case, output the number of "Mountain Number" between L and R in a single line.

 Sample Input



3

1 10

1 100

1 1000

 Sample Output



9

54

384

题意:给一对区间[l,r],让你求闭区间内Mountain数的个数。

Mountain数的定义:给你一个数a0a1a2a3...an,ai代表当前位的数字,任一奇数位的数比身旁偶数位的个数大,这即是Mountain数。

做法:数位dp之记忆化搜索

首先,我们先搞明白dp数组到底是干嘛用的,其实它是记录当前数位的状态(这个状态通常跟前面的数位有关)下能够推出多少种符合的情况,也就是说dp数组是承前启后的,记忆化搜索本质就是递归求解树,求这棵大树从根到叶子节点能够找到多少条符合的路径。假如dp数组是三维的,我们定义它为dp[a][b][c],记忆化搜索的时间复杂度为O(a*b*c)。

对于本题,我们定义dp数组为dp[pos][pre][parity],pos表示当前的位置,pre表示前一位的数字,parity表示,从无前导0的数字到下一状态位的奇偶性(注意数字是从a0开始的),奇数为1,偶数为0。

下面我们开始构造DFS函数:

一、确定参数:

1、首先是万年不变的pos,代表当前要算哪一位

2、然后是寻常见的pre,代表前一位的数字

3、parity,从没有前导0开始到下一状态的奇偶性

4、jud,是否算到了没有前导0的数位,

5、doing,是否到达上界

二、函数结构化的写法

1、万年不变,if(pos==-1) {怎么怎么滴}//不是return 1就是0

2、万年不变:if(!doing&&dp[][][]!=-1) return dp[][][];

3、确定边界end

4、for(i=0;i<=end;++i),符合条件的,则ans+=dfs()

5、如果当前没有到达上届,则把ans的值赋给dp[][][]

6、return ans

三、什么时候ans需要加上dfs()的状态下符合数的个数呢

1、当前位置仍然是前导0的情况下,我们还需要加,但是别忘了给pre定义为9,这样当它遇到非前导0的数位时,不会判断出错

2、当前位置已经不是前导0了(前面有非0的数字),当前位置是奇性的,下一位置等于或者小余这个位置的数字

3、当前位置已经不是前导0了(前面有非0的数字),当前位置是偶性的,下一位置等于或者大于这个位置的数字

然后我个人觉得cal函数不值一提,就不详细的写了。

#include<iostream>

#include<cstdio>

#include<cstring>

using namespace std;

int dp[12][10][2],digit[12];

int dfs(int pos , int pre,int parity,bool jud,bool doing) //doing为边界,pos为计算到当前第几位

{

  if(pos==-1) return 1;

  if(!doing&&dp[pos][pre][parity]!=-1)

      return dp[pos][pre][parity];

  int ans=0,end;

  end=doing?digit[pos]:9;

  for(int i=0;i<=end;++i)

  {

      /*

      jud表示前面有无前导0,pre参数必须为9啊,否则当它遇到第一个不是前导0的数字时,

      就判断出错了。那时候parity=0,必须使得pre>=i,

      */

      if(!(jud||i)) ans+=dfs(pos-1,9,0,jud||i,doing&&i==end); //如果此位仍然为前导0

      else if(parity&&pre<=i)  //如果下一状态为奇性的

          ans+=dfs(pos-1,i,parity^1,jud||i,doing&&i==end);

      else if(!parity&&pre>=i) //如果下一状态为偶性的

          ans+=dfs(pos-1,i,parity^1,jud||i,doing&&i==end);

  }

  if(!doing)

      dp[pos][pre][parity]=ans;

  return ans;

}

int cal(int x)

{

    int pos = 0;

    while(x)

    {

        digit[pos++] = x % 10;

        x /= 10;

    }

    return dfs(pos-1,9,0,0,1); //9很重要~~

}

int main()

{

    int t,l,r;

    cin>>t;

    memset(dp,-1,sizeof(dp));

    while(t--)

    {

        cin>>l>>r;

        printf("%d\n",cal(r)-cal(l-1));

    }

}

 

 

你可能感兴趣的:(mountain)