我滴头啊~晕啊晕。这题终于让我把数位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)); } }