题意:
给出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
参考博客:https://blog.youkuaiyun.com/baidu_27438681/article/details/52046610?locationNum=4&fps=1
#include<bits/stdc++.h>
using namespace std;
int n,m; //n个木棒,能被m整除
int cnt[]={6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
int dp[105][3005]; //dp[i][j]表示i位数字,除以m余数为j最少要的火柴数
int mod[10][105]; //mod[i][j]表示数字i,后面有j为位0,组成的数字模m的值
int main(){
int cs=1;
while(~scanf("%d",&n),n){
scanf("%d",&m);
printf("Case %d: ",cs++);
memset(dp,0x3f,sizeof dp);
dp[0][0]=0;
for(int i=0;i<=9;i++){
dp[1][i%m]=min(dp[1][i%m],cnt[i]);
}
for(int i=2;i<=n/2;i++){ //最多n/2位 全1
for(int j=0;j<m;j++){
for(int k=0;k<=9;k++){
dp[i][(j*10+k)%m]=min(dp[i][(j*10+k)%m],dp[i-1][j]+cnt[k]);
}
}
}
int ans=0;
for(int i=n/2;i>=0;i--){
if(dp[i][0]<=n){
ans=i;break;
}
}
if(ans==0){
printf("-1\n");
continue;
}
memset(mod,0,sizeof mod);
for(int i=0;i<=9;i++){
mod[i][0]=i%m;
for(int j=1;j<=100;j++){
mod[i][j]=mod[i][j-1]*10%m;
}
}
int tmp=0;
while(ans>0){
for(int i=9;i>=0;i--){
if(dp[ans-1][(m+tmp-mod[i][ans-1])%m]<=n-cnt[i]){
printf("%d",i);
n-=cnt[i];
tmp=(m+tmp-mod[i][ans-1])%m;
break;
}
}
ans--;
}
puts("");
}
}