题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=4734
F(x)
Time Limit: 1000/500 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4389 Accepted Submission(s): 1614
Problem Description
For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1
* 1. Now you are given two numbers A and B, please calculate how many
numbers are there between 0 and B, inclusive, whose weight is no more
than F(A).
Input
The first line has a number T (T <= 10000) , indicating the number of test cases.
For each test case, there are two numbers A and B (0 <= A,B < 109)
For each test case, there are two numbers A and B (0 <= A,B < 109)
Output
For every case,you should output "Case #t: " at first, without quotes. The t is the case number starting from 1. Then output the answer.
Sample Input
3
0 100
1 10
5 100
Sample Output
Case #1: 1
Case #2: 2
Case #3: 13
Source
第一次接触数位DP,本来是奔着记忆化搜去的。结果发现是个数位DP,收获挺大的。
简单的讲一下我的理解:
起根本还是一个记忆化搜索,主要难处理的地方是下一位的取值(一般是从高到低的遍历),比如说,456,第一位你取的是3,你下一位就可以去0~9,但是如果你取的是4,下一位你就只能取0~5了,这也是最巧妙的地方。
然后我找了一下网上的模板,记一下思路,写的很好。
int dfs(int i, int s, bool e) { if(i==-1) return s==target_s; if(!e && ~f[i][s]) return f[i][s]; int res = 0; int u = e?num[i]:9; for(int d = first?1:0; d <= u; ++d) res += dfs(i-1, new_s(s, d), e&&d==u); return e?res:f[i][s]=res; }
// pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位) // status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回, // 给计数器+1。 // limit = 是否受限,也即当前处理这位能否随便取值。如567,当前处理6这位, // 如果前面取的是4,则当前这位可以取0-9。如果前面取的5,那么当前 // 这位就不能随便取,不然会超出这个数的范围,所以如果前面取5的 // 话此时的limit=1,也就是说当前只可以取0-6。 // // 用DP数组保存这三个状态是因为往后转移的时候会遇到很多重复的情况。 int dfs(int pos,int pre,int status,int limit) { //已结搜到尽头,返回"是否找到了答案"这个状态。 if(pos < 1) return status; //DP里保存的是完整的,也即不受限的答案,所以如果满足的话,可以直接返回。 if(!limit && DP[pos][pre][status] != -1) return DP[pos][pre][status]; int end = limit ? DIG[pos] : 9; int ret = 0; //往下搜的状态表示的很巧妙,status用||是因为如果前面找到了答案那么后面 //还有没有答案都无所谓了。而limti用&&是因为只有前面受限、当前受限才能 //推出下一步也受限,比如567,如果是46X的情况,虽然6已经到尽头,但是后面的 //个位仍然可以随便取,因为百位没受限,所以如果个位要受限,那么前面必须是56。 // //这里用"不要49"一题来做例子。 for(int i = 0;i <= end;i ++) ret += dfs(pos - 1,i,status || (pre == 4 && i == 9),limit && (i == end)); //DP里保存完整的、取到尽头的数据 if(!limit) DP[pos][pre][status] = ret; return ret; }
#include<cstdio> #include<cstring> #define maxn 16 int dp[maxn][111111]; int d[maxn]; int n; long long tt; long long dfs(int len,int pre,bool fp) { if(pre<0) return 0; if(!len) return 1; if(!fp&&dp[len][pre]!=-1)return dp[len][pre];//记忆化搜索 int fpmax=fp?d[len]:9; int ret=0; for(int i=0; i<=fpmax; i++) { ret+= dfs(len-1,pre-i*(1<<(len-1)),fp&&i==fpmax); } if(!fp)dp[len][pre]=ret;//记录结果 return ret; } long long calc(long long a) { int len=0; memset(d,0,sizeof(d)); while(a) { d[++len]=a%10; a/=10; } return dfs(len,tt,true); } int get(int x) { int tmp=1; int ans=0; while(x) { ans+=(x%10)*tmp; x/=10; tmp<<=1; } return ans; } int main() { //freopen("input.txt","r",stdin); long long a,b; int nc; scanf("%d",&nc); int d=1; memset(dp,-1,sizeof(dp)); while(nc--) { scanf("%I64d%I64d",&a,&b); tt=get(a); printf("Case #%d: %I64d ",d++,calc(b)); } return 0; }