uva12105 【DP】

题目链接: 点击打开链接

【题意】用不超过n(n<=100)根火柴摆一个尽量大的能被m(m<=3000)整除的正整数。

 

【分析】可以用dp[i][j]表示除以m余j的i位数最少需要多少火柴这个状态计算,转移方程是:用dp[i][j]+c[k]来更新dp[i+1][(j*10+k)%m](c[]是每个数字需要花费的火柴数量,k是当前枚举的数字)。可以避免高精度提高效率,但是怎么确定每一位上的数字都是什么呢,需要用dp[i][0]找到最大的i使得dp[i][0]不是INF(初始化dp[][]为INF),这样就可以确定这个最大数字有几位了(位数多的肯定比位数少的大),然后在计算每一位上最大可以是什么数字,从大到小枚举每一位上的数字,第一个使得sum+dp[i-1][s]+c[j]<=n的数字就是该位上的最大值(其中s是去掉这一位上的数字剩下的几位的余数为s时使得这个总的数字能被m整除)。

比如,m = 7,并且已知当前数字位数为3,首先试着让最高位为9,如果可以摆出9ab这样的整数,那么一定是最大的,那么怎样确定能否摆出9ab呢?因为900%7 = 4,所以s,就是后两位'ab'%7应该等于3,(这里具体怎么算的下面再说),如果dp[2][3]+c[9] + sum<=n,(sum是已经确定的高位的数字的总花费),就说明火柴数量足够摆出9ab,否则最高位就不是9需要继续找,如果可以摆出那么重复这个过程直到算出每一位上的数字。还可以预处理计算出每个x00..这样数字%m的值用y数组保存,其实还是用到了一点高精度计算--大数取余。

 

现在就只有一个问题了,怎样算出s,就是已知当前整数为7ab%m = 0和700%m,求出ab%m的值,我计算了几个数字,找出了一个规律:

下面几位的余数s等于 m-当前这一位的数字x00..%m的值-v(前面所有已经确定的x00..%m之和)

比如:假设最大数字23450, m = 7

20000%7 = 1,3000%7 = 4, 400%7 = 1, 50%7 = 1, 0%7 = 0

2确定时 s(后4位%7) = (7-1-0)%7 = 6; v = 0+1 验证:3450%7 = 6

23确定时 s(后3位%7) = (7-4-1)%7 = 2; v = 1+4 验证: 450%7 = 2

234确定时 s(后2位%7) = (7-1-5)%7 = 1; v = 1+4+1 验证: 50%7 = 1

2345确定时 s(后1位%7) = (7-1-6)%7 = 0; v = 1+4+1+1 验证:0%7 = 0

需要注意一下v可能超过m,所以计算v时需要%m。计算s时可能为负数,需要先+m在%m


#include<cstring>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define  INF  0x3f3f3f3f
const int c[10] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
int l,dp[111][3111],n,m,f[111][11],falg,as[211];
void prin(int x)
{
    memset(as,-1,sizeof(as));
    int sum=0,l=m;
    for(int i=x;i>0;i--)
    {
        for(int j=9;j>=0;j--)
        {
            int y=(l-f[i][j]+m)%m;
            if(sum+dp[i-1][y]+c[j]<=n)
            {
                as[i]=j;
                sum+=c[j];
                l=((l-f[i][j])+m)%m;
                break;
            }
        }
        if(as[i]==-1)
        {
            if(n>=6)printf("0");
            else printf("-1");
            return ;
        }
    }
    for(int i=x;i>0;i--)
        printf("%d",as[i]);
    return ;
}
int main()
{
    int x,y,cas=1;
    while(scanf("%d",&n)!=EOF&&n)
    {
        scanf("%d",&m);
        if(m==0)
        {printf("Case %d: ",cas++);
            printf("-1\n");
            continue;
        }
        x=1;
        for(int i=1;i<=n/2;i++)
        {
            f[i][1]=x%m;
            f[i][0]=0;
            for(int j=2;j<=9;j++)
            {
                f[i][j]=f[i][1]*j%m;
            }
            x=x*10%m;
        }

        memset(dp,0x3f,sizeof(dp));
        int ans=-1;
         dp[0][0]=0;
         int w=n>>1;
        for(int i=0;i<w;i++)
            for(int j=0;j<m;j++)
        {
            if(dp[i][j]==INF)continue;
            for(int ll=0;ll<=9;ll++)
            {
                 if(dp[i][j]+c[ll]>n)continue;
                 y=(j*10+ll)%m;
                 dp[i+1][y]=min(dp[i+1][y],dp[i][j]+c[ll]);
            }
        }
        for(int i=n/2;i>0;i--)
        {
            if(dp[i][0]<=n)
            {ans=i;
             break;
            }
        }

//    cout<<dp[2][0]<<endl;
//       cout<<ans<<endl;
        printf("Case %d: ",cas++);
        if(ans==-1)
            printf("-1");
        else
        prin(ans);
        printf("\n");
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值