数位DP入门题
站点一览:
- hdu 2089"不要62"
- hdu 4734"F(X)"
- poj 3252"Round Numbers"
- 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 }
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 }
题意:
输入两个十进制正整数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\n",Solve(b)-Solve(a-1)); 47 48 return 0; 49 }
题意:
给一个很大的区间[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 }