UVA 12105 Bigger is Better(数位DP)

╰+哭是因爲堅強的太久メ 2021-12-15 00:57 315阅读 0赞

" class="reference-link">题意:å¨è¿éæå¥å¾çæè¿°

分析: 方法一:很容易想到,dp[i][j]代表用了i根火柴,除m余j的最大数。用刷表法,每次选择一个数k放在最右边,用dp[i][j]×10+k去更新dp[i+needs[k]][ ( j×10+k) % m], 其中needs[i]:i 所需要的火柴个数。不过因为有100个火柴,会涉及到大数。计算量挺大。 方法二:不好想。定义状态(i,j)表示用不超过i根火柴拼出“除以m余数为j”的整数的最大长度。设dp(i,j)。p(i,j)记录满足状态(i,j)的整数的最高位数字。 转移方程:dp(i,j) = max{ dp(i-needs[k] , (j*10+k)%m)+ } ( i >= needs[k] )。 上述方程表示当我们从高位往低位写数字的时候,假设当前写的数字是x,高位的数字除以m的余数为j,那么把x添加到前面写过的数字后面时候,相当于先对前一次写出来的数10,然后加上x,那么这个新数的余数就是(j*10+x)%m。由于dp值表示的位数,因此我们取位数最大的那个dp值,然后+1,就是状态(i,j)的最大长度,同时更新状态(i,j)对应的那个数字x,又因为我们希望写出的数尽可能的大,因此枚举x的h时候要从大到小枚举,而且必须满足c[x]<=i,其中c[x]表示写出数字x需要使用的火柴数。

注意一个优化点,由于高精度的计算上只需要乘10+k,常规的高精度乘法复杂度还是有点高会超时,所以用数组去模拟,每次*10 + k的时候就往后多一位即可。

参考:https://blog.csdn.net/cy05627/article/details/88748079、https://blog.csdn.net/corncsd/article/details/19929257。

代码(方法一):

  1. #include<bits/stdc++.h>
  2. #define INF 0x3f3f3f3f
  3. #define MAXN 110
  4. #define MAXM 3010
  5. using namespace std;
  6. int N,M;
  7. int v[10]={6,2,5,5,4,5,6,3,7,6};
  8. char dp[MAXN][MAXM][60],ans[60],s[60];
  9. int compare(char *a,char *b){
  10. int la=strlen(a),lb=strlen(b);
  11. if(la>lb) return 1;
  12. else if(la<lb) return -1;
  13. return strcmp(a,b);
  14. }
  15. void DP(char *a,char *b,int k){
  16. strcpy(s,b);
  17. int l=strlen(s);
  18. if(l==1&&s[0]=='0'){
  19. s[l-1]='0'+k;
  20. s[l]=0;
  21. }
  22. else{
  23. s[l]='0'+k;
  24. s[l+1]=0;///等价于 s[l+1]='\0'
  25. }
  26. if(compare(s,a)>0) strcpy(a,s);
  27. }
  28. int main(){
  29. int cas=0;
  30. while(scanf("%d",&N),N){
  31. scanf("%d",&M);
  32. memset(dp,0,sizeof(dp));/// 等价于 全部字符初始化为'\0';
  33. dp[0][0][0]='0';
  34. for(int i=0;i<=N;i++)
  35. for(int j=0;j<M;j++) if(strlen(dp[i][j])>0){///触发更新的条件
  36. for(int k=0;k<10;k++) DP(dp[i+v[k]][(j*10+k)%M],dp[i][j],k);///前面的状态更新后面的状态
  37. }
  38. ans[0]=0;
  39. for(int i=N;i>0;i--) if(compare(ans,dp[i][0])<0) strcpy(ans,dp[i][0]);
  40. printf("Case %d: ",++cas);
  41. if(ans[0]==0) puts("-1");
  42. else puts(ans);
  43. }
  44. return 0;
  45. }

LRJ代码(方法二):

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int maxn = 100 + 5;
  4. const int maxm = 3000 + 5;
  5. // dp[i][j] is the maximal length of the integer whose remainder is j (with at most i matches)
  6. // p[i][j] is the maximal digit for state (i,j)
  7. // p[i][j]表示这个数首位是什么
  8. int n, m, dp[maxn][maxm], p[maxn][maxm];
  9. int needs[] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
  10. int main()
  11. {
  12. int kase = 0;
  13. while(scanf("%d%d", &n, &m) == 2)
  14. {
  15. printf("Case %d: ", ++kase);
  16. // 预处理
  17. for(int i = 0; i <= n; i++)
  18. for(int j = 0; j < m; j++)
  19. {
  20. int& ans = dp[i][j];
  21. ans = p[i][j] = -1;
  22. if (j == 0) ans = 0;
  23. for(int d = 9; d >= 0; d--)
  24. if (i >= needs[d])
  25. {
  26. int t = dp[i - needs[d]][(j * 10 + d) % m];
  27. if (t >= 0 && t + 1 > ans) //t exist
  28. {
  29. ans = t + 1;
  30. p[i][j] = d;
  31. }
  32. }
  33. }
  34. if (p[n][0] < 0) printf("-1");
  35. else
  36. {
  37. int i = n, j = 0;
  38. for(int d = p[i][j]; d >= 0; d = p[i][j])
  39. {
  40. printf("%d", d);
  41. i -= needs[d];
  42. j = (j * 10 + d) % m;
  43. }
  44. }
  45. printf("\n");
  46. }
  47. return 0;
  48. }

发表评论

表情:
评论列表 (有 0 条评论,315人围观)

还没有评论,来说两句吧...

相关阅读

    相关 数位DP 详解

    序 > 天堂在左,战士向右 引言 数位DP在竞赛中的出现几率极低,但是如果不会数位DP,一旦考到就只能暴力骗分。 以下是数位DP详解,涉及到的例题有:

    相关 数位dp小结

    数位dp小结 关于limit的问题 在数位dp中,limit的作用主要有两个。 控制枚举的界限,倘若没有界限,每一位的枚举范围都是0-9. 但如果有界限,

    相关 数位DP

    Problem Description 据说,QAQ 的幸运数字是含有 "47" (4 和 7 相邻)的数,例如 47, 147, 247, 470, 471, 2047

    相关 数位DP UVA - 11038

    数位DP,顾名思义,是在个位,十位,百位,千位…….这些数的数位上进行的DP,它其实就是一种暴力枚举+记忆化搜索。 数位DP一般用来解决要求找出某个区间内,满足要求的数有多