uva12105 bigger is better

此文说两种方法,对应紫书中的第二种与第三种

先说第三种方法

dp[i][j]表示的状态是:剩余 i 根火柴,当前拼成的数余数是j

dp[i][j]表示的值是:已经拼成的数的位数

具体来讲,其数的形式为 YXXX,其中XXX部分表示已经拼成的数,j是XXX对于m的余数,而i是用来拼除已拼好的X部分的剩余部分的火柴数量

d从大到小枚举下一位,来保证拼成的数最大

d枚举的实际上就是最高位,也就是Y那一位

这样dp[i][j]的更新可以解释:
dp[i][j]被dp[i-拼出d位需要的火柴数][(j*10+d)%m]更新,也就是被dp[剩余的火柴数][新的余数]更新

其中新的余数指的是YXXX对m的余数,旧的余数j是XXX对m的余数
 


提一提初始化情况和边界情况:

dp[i][0]=0,原因是,余数为0,剩余的火柴数不管有多少,这都是一个有效的状态,所以其对应的值应该被纳入考虑范围

dp[i][不是0]和p[i][j]都应该初始化为-1,表示没有有效值


无法解释的情况

在dp[n][0]可能表示的所有数中,不会出现00000....,即两个或两个以上0叠加来达到二位数或多位数,最多出现一位数的0,也就是0本身

如果你读到这里想到了什么办法说明,不会出现0000....的情况的原因,请随时告诉我

同时,我也会不断思考这个问题

其实还有一种情况,即数据较弱


代码在紫书代码仓库中


第二种方法

dp[i][j]表示拼了i位数,余数是j,dp[i][j]的值表示需要的火柴棒数量

因为每次下一位d枚举的是较高位,所以若位数相同都是最多的位数,且使用的火柴棒数量符合要求,此时非0的数会掩盖0成为最高位,原因见上红字

所以,请忽略00000..0形式的数,这样的数不会作为结果出现

边界处理 d[0][0]=0,因为如果d[0][非0]也等于0了,计算d[1][0]的时候,新增的数d不是m的倍数的情况也会被算成合法情况,实际上,其不合法,其余的d[][]全部赋值为不可能值,可以取大于n的数

在预处理计算形容1000,30000,9000等时,以及,输出答案时,需要用到两个模运算的性质

(n*10)%m=((n%m)*(10%m))%m 

(a+b)%m=t 则 

b%m=(t-a%m+m)%m

#include<iostream>
#include<cstdio>
using namespace std;

#define inf 105

const int maxn=105,maxm=3005;
int dp[maxn][maxm],r[maxn][10];
int n,m,kase;
int need[15]={6,2,5,5,4,5,6,3,7,6};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    while(cin>>n>>m && n)
    {
        cout<<"Case "<<++kase<<": ";
        //preprocess r array
        for(int i=1;i<=9;i++) r[1][i]=i%m;
        for(int i=2;i<=n;i++)
        {
            for(int d=1;d<=9;d++)
            {
                r[i][d]=(r[i-1][d]*(10%m))%m;
            }
        }

        /*
        for(int i=0;i<=n;i++){
            for(int j=1;j<=9;j++)
                printf("r[%d][%d]=%d\n",i,j,r[i][j]);
        }
        */


        //initialize dp array
        for(int i=0;i<=n;i++) {
            for(int j=0;j<m;j++) dp[i][j]=inf;
        }
        dp[0][0]=0;

        for(int i=0;i<=n;i++){
            for(int j=0;j<m;j++){
                for(int d=9;d>=0;d--){
                    int &ans=dp[i+1][(j*10+d)%m];
                    ans=min(ans,dp[i][j]+need[d]);
                }
            }
        }

        /*
        for(int i=0;i<=n;i++){
            for(int j=0;j<m;j++){
                printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
            }
        }
        */

        int i;
        for(i=n;i>0;i--) if(dp[i][0]<=n) break;

        if(i==0) cout<<-1<<"\n";
        else{
            int t=0;
            for(;i>=1;i--){
                int d;
                for(d=9;d>=0;d--)
                {
                    int nxt=(t-r[i][d]+m)%m;
                    if(dp[i-1][nxt]+need[d]<=n)
                    {
                        n-=need[d];
                        t=nxt;
                        break;
                    }
                }
                cout<<d;
            }
            cout<<"\n";
        }


    }

    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值