UVa12105

来源:互联网 发布:3d软件培训 编辑:程序博客网 时间:2024/06/14 23:08

题目链接

简介:
用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;}
原创粉丝点击