# 数位DP入门题

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

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

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\n",Solve(b)-Solve(a-1));
47     }
48     return 0;
49 }
View Code

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\n",kase,Solve(b));
65     }
66     return 0;
67 }
View Code

输入两个十进制正整数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 的总个数中；

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)
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\n",Solve(b)-Solve(a-1));
47
48     return 0;
49 }
View Code

给一个很大的区间[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\n",Solve(b)-Solve(a-1));
57     }
58     return 0;
59 }
View Code

posted @ 2019-03-20 22:20 HHHyacinth 阅读(...) 评论(...) 编辑 收藏