1.HDU 4722 good numbers:
题意:给出一个区间【A,B】,求出区间内有多少个数的各位的和加起来模10等于0的数有多少个。
解法:这是一个数位DP简单入门题,简单的DFS+数组记忆化搜索就可以。姿势是自己写的。。感觉略搓,做到第三个数位DP题的时候看到了别人的姿势,学习了。
代码:
#include <iostream> #include <cstdio> #include <stack> #include <cstring> #include <queue> #include <algorithm> #include <cmath> //#include <unordered_map> #define N 22 using namespace std; typedef pair<long long,long long> PII; const long long INF=0x3f3f3f3f; void Open() { #ifndef ONLINE_JUDGE freopen("D:/in.txt","r",stdin); //freopen("D:/my.txt","w",stdout); #endif // ONLINE_JUDGE } long long dit[N]; long long dp[N][N][3]; long long dfs(long long idx,long long mod,bool limit,long long edidx) { if(dp[idx][mod][limit]!=-1) return dp[idx][mod][limit]; if(idx==edidx+1) { if(mod%10==0) { //cout<<1<<endl; return dp[idx][mod][limit]=1; } return dp[idx][mod][limit]=0; } long long ret=0; for(long long i=0;i<= (limit?dit[edidx-idx]:9);i++) { ret+=dfs(idx+1,(mod+i)%10,limit && i==dit[edidx-idx],edidx); } return dp[idx][mod][limit]=ret; } long long getval(long long x) { long long ditnum=0; memset(dp,-1,sizeof dp); while(x) { dit[ditnum++]=x%10; x/=10; } return dfs(1,0,1,ditnum); } int main() { Open(); long long T; scanf("%I64d",&T); int cas=1; while(T--) { long long a,b; scanf("%I64d%I64d",&a,&b); bool flag=false; long long ta=a; long long tmp=0; while(ta) { tmp+=(ta%10); ta/=10; } //cout<<tmp<<endl; if(tmp%10==0) flag=1; printf("Case #%d: %I64d\n",cas++,getval(b)-getval(a)+flag); } return 0; }
2.HDU 3555 Bomb
题意:"49"连在一起的数是good number,现在给你一个区间,让你找出区间中有多少个good number。
解法:仍然是一个简单数位DP,将上面一题的代码随便改改就可以过了。
代码:
#include <iostream> #include <cstdio> #include <stack> #include <cstring> #include <queue> #include <algorithm> #include <cmath> //#include <unordered_map> #define N 22 using namespace std; typedef pair<long long,long long> PII; const long long INF=0x3f3f3f3f; void Open() { #ifndef ONLINE_JUDGE freopen("D:/in.txt","r",stdin); //freopen("D:/my.txt","w",stdout); #endif // ONLINE_JUDGE } long long dit[N]; long long dp[N][3][3][3]; long long dfs(long long idx,bool ppre,long long pre,bool limit,long long edidx) { if(dp[idx][ppre][pre][limit]!=-1) return dp[idx][ppre][pre][limit]; if(idx==edidx+1) { if(ppre && pre == 1) { return dp[idx][ppre][pre][limit]=1; } return dp[idx][ppre][pre][limit]=0; } if(ppre && pre == 1) { long long cur=10; if(limit){ int tmpidx=idx; cur=dit[edidx-idx]; while(++tmpidx<=edidx) { cur*=10; cur+=dit[edidx-tmpidx]; } cur++; }else{ int tmpidx=idx; while(++tmpidx<=edidx) { cur*=10; } } return dp[idx][ppre][pre][limit]=cur; } long long ret=0; for(long long i=0;i <= (limit?dit[edidx-idx]:9);i++) { long long flag=0; if(i==4) flag=2; if(i==9) flag=1; ret+=dfs(idx+1,pre==2,flag,limit && i==dit[edidx-idx],edidx); } return dp[idx][ppre][pre][limit]=ret; } long long getval(long long x) { long long ditnum=0; memset(dp,-1,sizeof dp); while(x) { dit[ditnum++]=x%10; x/=10; } return dfs(1,0,0,1,ditnum); } int main() { Open(); long long T; scanf("%I64d",&T); while(T--) { long long a; scanf("%I64d",&a); printf("%I64d\n",getval(a)); } return 0; }
3.HDU 2089 不要62
题意:数字中有“4”或者“62”的数字是不吉利数字,仍然是给出一个区间,让我们求出有多少个吉利的数字。
解法:仍然是简单的数位DP。改改代码就过了。。
代码:
#include <iostream> #include <cstdio> #include <stack> #include <cstring> #include <queue> #include <algorithm> #include <cmath> //#include <unordered_map> #define N 22 using namespace std; typedef pair<long long,long long> PII; const long long INF=0x3f3f3f3f; void Open() { #ifndef ONLINE_JUDGE freopen("D:/in.txt","r",stdin); //freopen("D:/my.txt","w",stdout); #endif // ONLINE_JUDGE } long long dit[N]; long long dp[N][4][4][2]; long long dfs(long long idx,bool ppre,long long pre,bool limit,long long edidx) { if(dp[idx][ppre][pre][limit]!=-1) return dp[idx][ppre][pre][limit]; if(idx==edidx+1) { if((ppre && pre == 1) || pre==3) { //cout<<"1idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<1<<endl; return dp[idx][ppre][pre][limit]=1; } ///cout<<"2idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<0<<endl; return dp[idx][ppre][pre][limit]=0; } if((ppre && pre == 1) || pre==3) { long long cur=10; if(limit){ int tmpidx=idx; cur=dit[edidx-idx]; while(++tmpidx<=edidx) { cur*=10; cur+=dit[edidx-tmpidx]; } cur++; }else{ int tmpidx=idx; while(++tmpidx<=edidx) { cur*=10; } } //cout<<"3idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<cur<<endl; return dp[idx][ppre][pre][limit]=cur; } long long ret=0; for(long long i=0;i <= (limit?dit[edidx-idx]:9);i++) { long long flag=0; if(i==6) flag=2; if(i==2) flag=1; if(i==4) flag=3; ret+=dfs(idx+1,pre==2,flag,limit && i==dit[edidx-idx],edidx); //ret+=dfs(idx+1,pre,i,limit && i==dit[edidx-idx],edidx); } //cout<<"4idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<ret<<endl; return dp[idx][ppre][pre][limit]=ret; } long long getval(long long x) { long long ditnum=0; memset(dp,-1,sizeof dp); while(x) { dit[ditnum++]=x%10; x/=10; } return dfs(1,0,0,1,ditnum); } int main() { Open(); long long n,m; while(~scanf("%I64d%I64d",&n,&m) && (n||m)) { //cout<<a<<endl; printf("%I64d\n",m-n+1-getval(m)+getval(n-1)); } return 0; }
4.HDU 4734 F(x)
题意:
定义:
F(x) = A n * 2 n-1 +
A n-1 * 2 n-2 +
... + A 2 * 2
+ A 1 * 1.
给出A,B,问0~B中有多少个数不大于F(A)。
解法:
注意到此题的数据组数非常巨大(10000组),所以我们如果再用上面两种方法的时候就会T,因为在上面的方法中,dp记忆化搜索数组将“limit”这个值也计算进
去了,也就是说,每一组数据的dp数组我们都要重新初始化,重新计算。当我观察了别的菊苣的代码之后,发现原来数位DP正确的姿势应该不是我那样的,而是
DP数组能够利用在不同的数据组中,也就是说多组数据的时候,我们也不用每次都将DP数组初始化。如此,我们的复杂度便到了可接受的地步。
回到正题,我们要求0~B中有多少个数小于F(A)的话,当然首先得将F(A)算出来,在DFS过程中,我们记录idx,sum,limit,idx表示当前是第几位,sum表示前idx位
的和,而limit的作用不再赘述,大家都懂。此时的复杂度比较小。O(idx*sum)是可以接受的。
代码:
#include <iostream> #include <cstdio> #include <stack> #include <cstring> #include <queue> #include <algorithm> #include <cmath> //#include <unordered_map> #define N 21 using namespace std; typedef pair<long long,long long> PII; const long long INF=0x3f3f3f3f; void Open() { #ifndef ONLINE_JUDGE freopen("D:/in.txt","r",stdin); //freopen("D:/my.txt","w",stdout); #endif // ONLINE_JUDGE } long long valA; long long dit[N]; long long dp[N][11110]; long long dfs(long long idx,long long sum,bool limit) { if(sum<0) return 0; if(idx == -1) { return sum>=0; } if(!limit && dp[idx][sum]!=-1) return dp[idx][sum]; long long ret=0; int ed=limit?dit[idx]:9; for(long long i=0;i <= ed;i++) { ret+=dfs(idx-1,sum-i*(1<<idx),limit && i==dit[idx]); } if(!limit) return dp[idx][sum]=ret; return ret; } long long getval(long long x) { long long ditnum=0; while(x) { dit[ditnum++]=x%10; x/=10; } return dfs(ditnum-1,valA,1); } int main() { Open(); long long T; scanf("%I64d",&T); long long n,m; long long cas=1; memset(dp,-1,sizeof dp); while(T--) { scanf("%I64d%I64d",&n,&m); valA=0; int dig=0; while(n) { valA+=(n%10)*(1<<dig); dig++; n/=10; } printf("Case #%I64d: %I64d\n",cas++,getval(m)); } return 0; }
5.HDU 4389 X mod f(x)
题意:
还是给出一个区间【A,B】,求出区间内有多少个数x满足 X mod f(X) ==0。其中F(x)表示X十进制表示下的各位数位的数的和。
解法:
此题与前四个题目比起来难了一些。在这里DFS的方法并不好想,所以用了数位DP直推的方法写。定义dp[len][i][j][k];表示len位的数 F(x)==i 的数模 j 的结果为k的
数有多少个。这样的话,我们首先预处理出dp数组。递推方程式比较容易出的。然而问题是预处理了dp数组之后我们该怎么得到结果。
具体见代码注释:
#include <iostream> #include <cstdio> #include <stack> #include <cstring> #include <queue> #include <algorithm> #include <cmath> //#include <unordered_map> #define N 11 using namespace std; typedef pair<int,int> PII; const int INF=0x3f3f3f3f; void Open() { #ifndef ONLINE_JUDGE freopen("D:/in.txt","r",stdin); //freopen("D:/my.txt","w",stdout); #endif // ONLINE_JUDGE } int valA; int dit[N]; //int dp[N][4][4][2]; int dp[N][91][91][91];//dp[len][i][j][k]前len位数和为i模j结果为k的数的个数 void init() { for(int i=1;i<=81;i++) dp[0][0][i][0]=1; for(int len=0;len<9;len++) for(int i=0;i<=9*len;i++) for(int j=1;j<=81;j++) for(int k=0;k<j;k++) for(int dig=0;dig<10;dig++){ dp[len+1][i+dig][j][(k*10+dig)%j] += dp[len][i][j][k]; //cout<<dp[len][i][j][k]; } } int ten[12]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; int getval(int x) { int ditnum = 0; int ans = 0; int tmpx = x; //得到x的十进制数组 while(tmpx) { dit[ditnum++]=tmpx%10; tmpx /= 10; } int presum=0; int preNum=0; for(int i=ditnum-1;i>=0;i--)//遍历x的各个数位。 { int ret=0; for(int j=0;j<dit[i]+(i==0);j++)//对小于x的每个数都进行处理 { //cout<<i<<","<<j<<endl; for(int k=j+presum;k<=81;k++)//枚举每一个可作为mod的数。从j+presum开始,(j+presum)表示当前位之前的数的和。 { if(!k)//如果k==0,表示不符合条件。 continue; int r=preNum%k; //得到前面几位数字%k说得到的余数。 r=(k-r)%k; //这样的(r+(preNum%k))%k==0 一定成立 ans+=dp[i][k-j-presum][k][r]; } preNum+=ten[i]; //更新preNum //cout<<"here:"<<preNum<<endl; } presum+=dit[i]; //更新presum } return ans; } int main() { Open(); int T; scanf("%d",&T); int n,m; int cas=1; init();//预处理出dp数组 //memset(dp,-1,sizeof dp); while(T--) { scanf("%d%d",&n,&m); printf("Case %d: %d\n",cas++,getval(m)-getval(n-1)); } return 0; }