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