题目链接: 点击打开链接
【题意】用不超过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;
}