数位DP入门题

站点一览:

  1. hdu 2089"不要62"
  2. hdu 4734"F(X)"
  3. poj 3252"Round Numbers"
  4. hdu 3709"Balanced Number"

1.hdu 2089"不要62"

题解:

  题目过于简单,不再赘述。

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 #define mem(a,b) memset(a,b,sizeof(a))
 6 
 7 int a,b;
 8 int digit[10];
 9 int dp[10][2];//dp[i][0]:位置i的前一个位置不为6的总方案数;dp[i][1]正好相反 
10 
11 int DFS(int curPos,int preNum,int isSix,bool limit)
12 {
13     if(curPos == -1)
14         return 1;
15     if(!limit && dp[curPos][isSix] != -1)
16         return dp[curPos][isSix];
17         
18     int up=limit ? digit[curPos]:9;
19     int ans=0;
20     for(int i=0;i <= up;++i)
21     {
22         if((preNum == 6 && i == 2) || i == 4)
23             continue;
24          ans += DFS(curPos-1,i,i == 6,limit&& i == digit[curPos]);
25     }
26     if(!limit)
27         dp[curPos][isSix]=ans;
28     return ans;
29 }
30 int Solve(int x)
31 {
32     int k=0;
33     while(x)
34     {
35         digit[k++]=x%10;
36         x /= 10;
37     }
38     return DFS(k-1,0,0,true);
39 }
40 
41 int main()
42 {
43     while(~scanf("%d%d",&a,&b) && a+b)
44     {
45         mem(dp,-1);
46         printf("%d
",Solve(b)-Solve(a-1));
47     }
48     return 0;
49 }
View Code

2.hdu 4734"F(X)"

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)

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.

题解:

  初始想法:

  定义dp[ i ][ j ] : [ 0,i ] 位置满足 weight == j 的数的总个数;

  求出F(a)后,便利 i : 0~F(a) ,求出 [0,b] weight == i 的数的总个数,作加和;

  很不幸,TLE,不过,还是挺开心的,毕竟是在看题解前按照自己的想法成功敲出的代码,虽然TLE了;

  正解:定义dp[ i ][ j ] : [ 0,i ]位置满足 weight <= j 的数的总个数,然后,每次判断 dp[ i ][ j ]是否可以直接返回;

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 #define mem(a,b) memset(a,b,sizeof(a))
 6 
 7 int a,b;
 8 int power[10]={1,2,4,8,16,32,64,128,256};
 9 int digit[15];
10 int dp[15][30000];//dp[i][j]:前i位F值小于等于j的总个数
11 
12 int F(int x)
13 {
14     int sum=0;
15     int base=1;
16     while(x)
17     {
18         sum += (x%10)*base;
19         base <<= 1;
20         x /= 10;
21     }
22     return sum;
23 }
24 int DFS(int need,int curPos,int curSum,bool limit)
25 {
26     if(curPos == -1)
27         return curSum <= need ? 1:0;
28         
29     if(curSum > need)
30         return 0;
31         
32     if(!limit&&dp[curPos][need-curSum] != -1)
33         return dp[curPos][need-curSum];
34 
35     int up=limit ? digit[curPos]:9;
36     int ans=0;
37     for(int i=0;i <= up;++i)
38         ans += DFS(need,curPos-1,curSum+i*power[curPos],limit&&i==digit[curPos]);
39         
40     if(!limit)
41         dp[curPos][need-curSum]=ans;
42 
43     return ans;
44 }
45 int Solve(int x)
46 {
47     int k=0;
48     while(x)
49     {
50         digit[k++]=x%10;
51         x /= 10;
52     }
53     int f=F(a);
54     return DFS(f,k-1,0,true);
55 }
56 int main()
57 {
58     int test;
59     scanf("%d",&test);
60     mem(dp,-1);
61     for(int kase=1;kase <= test;++kase)
62     {
63         scanf("%d%d",&a,&b);
64         printf("Case #%d: %d
",kase,Solve(b));
65     }
66     return 0;
67 }
View Code

3.poj 3252"Round Numbers"

题意:

  输入两个十进制正整数a和b,求闭区间 [a ,b] 内有多少个Round number

  所谓的Round Number就是把一个十进制数转换为一个无符号二进制数,若该二进制数中0的个数大于等于1的个数,则它就是一个Round Number

  注意,转换所得的二进制数,最高位必然是1,最高位的前面不允许有0

题解:

  定义dp[ i ][ j ][ k ] : 在没有限制的情况下,[0,i]位置满足 0 的个数等于 j,1 的个数等于 k 的数的总个数;

  坑点:前导零不能算在 0 的总个数中;

因为poj炸了,所以一直处于wait状态,不过对拍了一下他人的AC代码,正确率是可以保证的,应该也不会超时吧orz;

wait代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 #define mem(a,b) memset(a,b,sizeof(a))
 6 
 7 int a,b;
 8 int digit[35];
 9 int dp[35][35][35];
10 
11 //isSat:只有当出现第一个不为0的位数时才为true
12 //作用是去掉前导0的影响 
13 int DFS(int curPos,int totZero,int totOne,bool isSat,bool limit)
14 {
15     if(curPos == -1)
16         return totZero >= totOne;
17         
18     if(!limit && dp[curPos][totZero][totOne] != -1)
19         return dp[curPos][totZero][totOne];
20         
21     int up=limit ? digit[curPos]:1;
22     int ans=0;
23     for(int i=0;i <= up;++i)
24     {
25         bool flag=(isSat || i != 0);
26         ans += DFS(curPos-1,totZero+(flag&&i==0),totOne+(i==1),flag,limit&&i==digit[curPos]);
27     }
28     if(!limit)
29         dp[curPos][totZero][totOne]=ans;
30     return ans;
31 }
32 int Solve(int x)
33 {
34     int k=0;
35     while(x)
36     {
37         digit[k++]=x%2;
38         x >>= 1;
39     }    
40     return DFS(k-1,0,0,false,true);
41 }
42 int main()
43 {
44     mem(dp,-1);
45     while(~scanf("%d%d",&a,&b))
46         printf("%d
",Solve(b)-Solve(a-1));
47         
48     return 0;
49 }
View Code

4.hdu 3709"Balanced Number"

题意:

  给一个很大的区间[x,y],(0 ≤ x ≤ y ≤ 1018).问:区间里面的数满足如下规则的有多少个?

  规则:将数字放在天平上,天平能够平衡。天平的轴随意,力臂就是数字下标到天平轴的下标的距离。

题解:

  脑海中浮现出如何记忆化搜索的代码,可就是没想到如何判断当前数是否为"balance number",无奈之下,查阅大佬代码orz;

  坑点:全为0的数会重复计算,需要减去重复的部分

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 #define ll long long
 6 #define mem(a,b) memset(a,b,sizeof(a))
 7 
 8 ll a,b;
 9 int digit[20];
10 ll dp[20][20][1800];
11 
12 //curSum:记录curPivot左边的和+右边的和,只有curSum == 0才能说明
13 // curPivot左边的和==右边的和 
14 ll DFS(int curPos,int curPivot,int curSum,bool limit)
15 {
16     if(curPos == -1)
17         return curSum == 0 ? 1:0;
18     if(curSum < 0)
19         return 0;
20     if(!limit && dp[curPos][curPivot][curSum] != -1)
21         return dp[curPos][curPivot][curSum];
22         
23     int up=limit ? digit[curPos]:9;
24     ll ans=0;
25     for(int i=0;i <= up;++i)
26         ans += DFS(curPos-1,curPivot,curSum+i*(curPos-curPivot),limit&&i==digit[curPos]);
27     
28     if(!limit)
29         dp[curPos][curPivot][curSum]=ans;
30     return ans;
31 }
32 ll Solve(ll x)
33 {
34     if(x == -1)
35         return 0;
36     int k=0;
37     while(x)
38     {
39         digit[k++]=x%10;
40         x /= 10;
41     }
42     ll ans=0;
43     for(int i=k-1;i >= 0;--i)
44         ans += DFS(k-1,i,0,true);
45     return ans-k+1;
46     //0个0,1个0,.......,(k-1)个0,全为0的情况被记录了k次,须减去(k-1)个重复的 
47 }
48 int main()
49 {
50     int test;
51     scanf("%d",&test);
52     mem(dp,-1);
53     while(test--)
54     {
55         scanf("%lld%lld",&a,&b);
56         printf("%lld
",Solve(b)-Solve(a-1));
57     }            
58     return 0;
59 }
View Code
原文地址:https://www.cnblogs.com/violet-acmer/p/10568181.html