UVA12105 Bigger is Better 详解

博客详细解析了UVA12105 Bigger is Better的解题思路,包括三种方法:方法一使用高精度计算,方法二优化状态转移,方法三调整状态定义。所有方法都基于动态规划,目标是用有限火柴拼出能被特定数整除的最大数。

UVA12105 Bigger is Better

题目描述:紫书P293

用不超过 n(1≤n≤100)n(1 \leq n \leq 100)n1n100 根火柴摆出一个尽量大的、能被 m(1≤m≤3000)m(1 \leq m \leq 3000)m1m3000 整除的数。
在这里插入图片描述

解题思路

方法一:

来自紫书,设dp(i,j)dp(i,j)dp(i,j)表示用iii根火柴能拼出除以mmm余数为jjj的最大的数,枚举在最右边添加数字kkk,用dp(i,j)∗10+kdp(i,j)*10+kdp(i,j)10+k去更新dp(i+c(k),(j∗10+k)%m)dp(i+c(k),(j*10+k)\%m)dp(i+c(k),(j10+k)%m),其中c(k)c(k)c(k)表示数字kkk需要的火柴数。但是由于最多能有50位,需要高精度。

方法二:

也来自紫书,dp(i,j)dp(i,j)dp(i,j)表示拼出一个“除以mmm余数为jjjiii位数”至少需要多少根火柴,状态转移和上面差不多,我们也枚举最后一位kkk,用dp(i,j)+c[k]dp(i,j)+c[k]dp(i,j)+c[k]去更新dp(i,(j∗10+k)dp(i,(j*10+k)%m)dp(i,(j10+k)即可。
这时我们知道了答案是几位数,那么接下来从高位开始依次输出,因为要让这个数尽量大,所以对于每一位我们都按9,8,7…降序依次判断是否可行,例如m=7m=7m=7,并已经确定了最大位数为3位数。首先试着让最高位为9,如果能摆出形如9ab的整数,那它一定数最大的,又因为900除以7的余数为4,所以后两位(ab)除以7的余数应该为3,如果d(2,3)+c(9)≤nd(2,3)+c(9) \leq nd(2,3)+c(9)n即可。
如何计算出x00…除以mmm的余数需要预处理,还要注意注意最高位不能为0。

方法三:

dp(i,j)dp(i,j)dp(i,j)表示用不超过iii根火柴摆成“还需要剩余部分除以mmm余数为jjj”的最大位数(当前余数是m-j)。

状态转移就是dp(i,j)=max(dp[i−c[k]][(j∗10+k)%m]),(0≤k≤9)dp(i,j)=max(dp[i-c[k]][(j*10+k)\%m]),(0\leq k \leq9)dp(i,j)=max(dp[ic[k]][(j10+k)%m])(0k9)(自己想下其实还是很好理解的和方法二的差不多)。为了方便输出,再定义一个p(i,j)p(i,j)p(i,j)表示dp(i,j)dp(i,j)dp(i,j)状态时的最大位的数是多少,边界条件dp(j,0)=0dp(j,0)=0dp(j,0)=0,如果我当前的数已经符合条件,那么我可以不再加,也就是余数为0时,长度为0,因为dp(i,j)dp(i,j)dp(i,j)中的iii表示的是不超过iii,表示即使我不在再添加数字了也可以有多出来的火柴,这时dp(j,0)dp(j,0)dp(j,0)jjj表示的就是哪些多出来的火柴。(如果dp(i,j)dp(i,j)dp(i,j)iii表示的是刚好用iii根火柴,那么只需要dp(0,0)=0dp(0,0)=0dp(0,0)=0)。

dp(i,j)dp(i,j)dp(i,j)表示剩余iii根(已经用了n−in-ini根)余数为jjj的最大位数也是一样的,不过是从高位开始枚举,有兴趣的可以试试。

代码快

方法一大数没去做,方法二写的有点乱,没整理,这里只提供方法三的

#include <bits/stdc++.h>
using namespace std;
int dp[105][3005];
int p[105][3005];
int c[]={6,2,5,5,4,5,6,3,7,6};
int main(){
	int n,m,cas=0;
	while(cin>>n&&n)
	{
		cin>>m;
		memset(dp,-1,sizeof(dp));
		for(int i=0;i<=n;i++)
			dp[i][0]=0;
		int ans=-1,x,y;
		for(int i=1;i<=n;i++) 
			for(int j=0;j<m;j++)
				for(int k=9;k>=0;k--)
					if(i>=c[k])
					{
						if(dp[i-c[k]][(j*10+k)%m]>=0&&dp[i][j]<(dp[i-c[k]][(j*10+k)%m]+1))
						{
							dp[i][j]=dp[i-c[k]][(j*10+k)%m]+1;
							p[i][j]=k;
						}
					}

		ans=dp[n][0];
		printf("Case %d: ",++cas);
		if(ans<=0){
			printf("-1\n");continue;
		}
		x=n;y=0;
		while(ans--)
		{
			int e=p[x][y];
			printf("%d",e);
			x-=c[e];
			y=(y*10+e)%m;
		}
		printf("\n");
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值