UVA - 12105 Bigger is Better ( DP )

火柴拼数DP算法
本文探讨了使用动态规划算法解决火柴拼成的最大整数问题,尤其关注于该整数能被特定数值m整除的情况。通过巧妙设计的状态转移方程,文章详细解释了如何在限制条件下找到最优解,同时提供了高效的实现方案,避免了高精度计算带来的复杂性。

题意:

给出n个木棒,摆出能被m整除的最大的数

分析:

有一种非常显而易见的dp方法: 
f[i][j]表示用i根火柴拼出的“%m是j”的最大整数 
转移方程:

f[i+c[k]][(j*10+k)%m]=max{f[i][j]+k}
时间复杂度是O(10*nm),看上去好像非常优秀 
但是这样的状态值是高精度(能拼成的数可能很大),因此实际计算量非常大 
(再说有人愿意随手写一个高精度吗。。。)

下面就说一个有点难度,但是效率很高的算法: 
f[i][j]表示拼出“%m是j的i位数”所用的最少火柴 
转移方程:

f[i+1][(j*10+k)%m]=min{f[i][j]+c[k]}
那么我们怎么根据f数组确定解呢 
首先我们先确定下来最高位(在转移的时候顺便维护一下就好了) 
之后我们从高位向低位枚举每一位的数字 
举个简单的例子:m=7,并且已经确定最高位是3 
首先试着让最高位为9,如果我们能够摆出9xx这样的合法数字,那么ta一定是最大的 
是否可以摆出9xx呢? 
因为900%7=4,因此后两位%7的余数应该是3 
如果d[2][3]+c[9]<=n,那么最高位就可以是9

重复上述过程,直到所有数字都被确定 
在计算过程中,我们需要快速算出形如 x000… 的整数%m的答案,这需要我们一开始预处理一下

tip
d<—INF,d[0][0]=0

注意
我们计算出来的f只是辅助构造解的 
我们在构造解的时候运用贪心的思想,让位数尽量多,每一位尽量大 
所以只要符合

d[now-1][(limit-c[k]+m)%m]+c[k]<=n

参考博客:https://blog.youkuaiyun.com/baidu_27438681/article/details/52046610?locationNum=4&fps=1

#include<bits/stdc++.h>

using namespace std;

int n,m;        //n个木棒,能被m整除
int cnt[]={6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
int dp[105][3005];  //dp[i][j]表示i位数字,除以m余数为j最少要的火柴数
int mod[10][105];   //mod[i][j]表示数字i,后面有j为位0,组成的数字模m的值

int main(){
    int cs=1;
    while(~scanf("%d",&n),n){
        scanf("%d",&m);
        printf("Case %d: ",cs++);
        memset(dp,0x3f,sizeof dp);
        dp[0][0]=0;
        for(int i=0;i<=9;i++){
            dp[1][i%m]=min(dp[1][i%m],cnt[i]);
        }
        for(int i=2;i<=n/2;i++){        //最多n/2位  全1
            for(int j=0;j<m;j++){
                for(int k=0;k<=9;k++){
                    dp[i][(j*10+k)%m]=min(dp[i][(j*10+k)%m],dp[i-1][j]+cnt[k]);
                }
            }
        }
        int ans=0;
        for(int i=n/2;i>=0;i--){
            if(dp[i][0]<=n){
                ans=i;break;
            }
        }
        if(ans==0){
            printf("-1\n");
            continue;
        }
        memset(mod,0,sizeof mod);
        for(int i=0;i<=9;i++){
            mod[i][0]=i%m;
            for(int j=1;j<=100;j++){
                mod[i][j]=mod[i][j-1]*10%m;
            }
        }
        int tmp=0;
        while(ans>0){
            for(int i=9;i>=0;i--){
                if(dp[ans-1][(m+tmp-mod[i][ans-1])%m]<=n-cnt[i]){
                    printf("%d",i);
                    n-=cnt[i];
                    tmp=(m+tmp-mod[i][ans-1])%m;
                    break;
                }
            }
            ans--;
        }
        puts("");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值