UVa12105 - Bigger is Better(数位|贪心)

112 篇文章 0 订阅
19 篇文章 0 订阅

题目链接

简介:
用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

我们就可以确定这一位是k

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int INF=0x33333333;
int c[10]={6,2,5,5,4,5,6,3,7,6};
int d[100][3002];
int n,m,len,maxx,rst[100][10];

void doit()
{
    int i,j,k;
    d[0][0]=0;      //不要忘了初始化 
    for (i=0;i<=9;i++)
    {
        d[1][i%m]=min(d[1][i%m],c[i]);
        if (d[1][i%m]<=n&&i%m==0) maxx=1;
    } 
    for (i=1;i<len;i++)
        for (j=0;j<m;j++)
            if (d[i][j])
            for (k=0;k<=9;k++)
            {
                int v=(j*10+k)%m;
                d[i+1][v]=min(d[i][j]+c[k],d[i+1][v]);
                if (d[i+1][v]<=n&&v==0) maxx=max(maxx,i+1);   //最高位 
            }
}

void print()
{
    int ans[100],limit=m;
    int S=n;                                     //可用的火柴 
    for (int i=maxx;i>=1;i--)
        for (int j=9;j>=0;j--)                   //从大到小枚举这一位上的数 
        {
            int r=S-c[j];                        //剩下的位数能够使用的火柴数 
            int num=(limit-rst[i][j]+m)%m;       //剩下的位数%m的余数 
            if (d[i-1][num]<=r)                  //只要可行即可 
            {
                ans[i]=j;
                S-=c[j];
                limit=num;
                break;
            }
        }
    for (int i=maxx;i>=1;i--) printf("%d",ans[i]);
    printf("\n");
}

int main()
{
    int cnt=0;
    while (scanf("%d",&n)!=EOF&&n)
    {
        scanf("%d",&m);
        memset(d,0x33,sizeof(d));
        memset(rst,0,sizeof(rst));

        for (int i=1;i<=9;i++) rst[1][i]=i%m;      //预处理 x000 %m 
        for (int i=2;i<100;i++)
            for (int j=1;j<=9;j++)
            {
                int num=(rst[i-1][j]*10)%m;
                rst[i][j]=num;
            }   

        printf("Case %d: ",++cnt);
        len=n/2+1; maxx=0;
        doit();
        if (d[maxx][0]<=n&&maxx) print();
        else printf("-1\n");
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值