牛客BC120 小乐乐与二段数的一些思考与补充


题目

  二段数为仅包含两种十进制数字的s和t,且s不为0,s所有均出现在所有t的前面。举个例子:4444444111是二段数,51、10000000000和5555555556666也是,这是小乐乐被老师问道:给任意一个正整数n,求出比n大且是n倍数的最小二段数?


提示:以下是本篇文章正文内容,下面案例可供参考

一、题意分析

  这道题理解起来还是有一定难度的,看一眼往往很懵!不过分开来理解其实并不难,这样来看,首先二段数是一个特定设置的长串数字,例如:ssssssssstttttt(9个s和6个t,我们可以这样还原该长串数字,即:111111111*10^6*s+111111*t,这样看,是不是清楚很多!
简要示意   上面是第一个难点,第二个难点便是n的倍数问题,有的数字过于冗长,按照原始的计算方法不能及时有效的获取结果,例如:要寻找2019的n的倍数,通过计算可以得到既是2019的倍数又是二段数的结果为9999999993(9个9,1个3),这要计算下来需要浪费多少内存啊!所以,我将在下面的内容中着重说明,这个问题,并对网上所找到的代码简单说一下自己的一些理解。

二、代码及解读

1.数字过大,除余有妙招

  先来看一下下面这段数学推算,这里假定输入的数字为2019:
  1%2019=1;
  11%2019=11;
  111%2019=111;
  1111%2019=1111;
——————————
  现在看好了!
令a =11111%2019=(1111*10+1)%2019=(1111%2019*10+1)%2019
则:
b=111111%2019=(11111%2019*10+1)%2019=(a*10+1)%2019
c=1111111%2019=(111111%2019*10+1)%2019=(b*10+1)%2019
。。。。。。
b简化后的式子,充分利用了a计算的结果,需要明白a小于输入的数字2019,这样就将一个6位数的除法降位为5位数的除法,秒啊!!!!!
  所以利用此等关系逻辑,便可以极大地缩减运算量,可以通过预先查表的方法,实现数据的快速映射,获得所需的的结果,下面我们用代码来实现这部分功能。抛开整个代码的总体轮廓,我们先对其中这部分问题的代码进行详细解读。

//根据二段数的特性,设定两个矩阵:a[9999]与b[999]
int a[9999],b[999]
int m,total,s,t,aptotal,apm,aps,apt,k;
int n; 
//n为输入的数字 如上文所提到的 2019
//total为二段数的总位数
//m,total-m,s,t 分别对应 上文图中所绘制的 表达二段数所需的四个参数
//apm,aptotal-apm,aps,apt 分别对应最后判断筛选完成后的 二段数的样子


int main()
{
	while(scanf("%d",&n),n)
	{
		printf("%d: ", n);
		if (n == 1) {
			puts("10");
			continue;
		}

		//a[0] 至 a[3] 分别为 1、11、111、1111 
		//从a[4]起,将会利用1111的计算结果进行后续的推算
		//即使a[4]并不是11111,但其代表11111%2019的结果
		//同理b[i]也是这样,其必要性来自于这个公式,111111111\*10^6\*s+111111*t。
		//a[i]用来对应式子中的1111...11
		//b[i]则对应式中的10^6
		
		//a[i]为(i+1)个1除以N的余数
		//b[i]就是10的i次方除以N的余数。
		a[0]=1;
		b[0]=1;
		for(int i=1;i<9999;i++)
			a[i]=(a[i-1]*10+1)%n;
		for (int i = 1; i < 999; i++)
			b[i] = b[i - 1] * 10 % n;
		
		//下式为还原二段数与判断的结果
		//可以看到充分利用了我们所提到的简化运算
		//这里我曾疑惑了好久,那便是还原后的数字不为原数,可以进行验证
		//输入的数字为2019,判断正确后,通过下式返回的数字为 135273 当然该数字为2019的整倍数,其就代表了 9999999993 % 2019,哈哈 非常有趣啊!
		(((long long)a[m]) * b[total - m] * s + a[total - m - 1] * t) % n == 0 
		
		//下面的内容便是最后的输出,考虑到数组从0下标开始,所以apm需要加1
		for (int x = 0; x < apm + 1; x++)
			printf("%d", aps);
		for (int x = 0; x < aptotal - apm; x++)
			printf("%d", apt);
		printf("\n");
	}
}

2. 整体代码理解

  这部分的内容较多,我对其理解不是特别透彻,只能就其中清晰的部分,进行解读,哪里不正确欢迎各位补充和纠正啊!
  虽然,我们对运算进行了极大地简化,但考虑到输入特殊数字的情况,需要“因地制宜”,我这里不知用什么名词比较恰当,应该说对10与25成倍数的数字,予以特殊“关怀”。
  空说无意,我们结合代码来看!

#include <stdio.h>


int m,total,s,t,aptotal,apm,aps,apt,k;//我们让total来存储这个数的位数 这样n就可以用total-m来赋值
int n; //注意这里的n是我们要输入的值

//由于使用到了全局变量,所以这里不用传递函数参量
//这部分内容主要是还原二段数,判断其是否满足大于输入数字(如:2019)

int ck()
{
	int p,r;
	//我们让p储存s,r存储t
	if (total > 5)
		return 1;
	p=s;
	r=t;
	for(int q=0;q<m;q++)
	{
		p=p*10+s;
	}
	//我们需要将p扩10的total-m次方
	for (int q = 0; q < total-m; q++)
		p = p * 10;
	for (int q = 1; q < total-m; q++)
		r = r * 10 + t;
	return p+r>n;
}

int main()
{
	while(scanf("%d",&n),n)
	{
		printf("%d: ", n);
		if (n == 1) {
			puts("10");
			continue;
		}
		//下面的数组可以参见上述理解
		a[0]=1;
		b[0]=1;
		for(int i=1;i<9999;i++)
			a[i]=(a[i-1]*10+1)%n;
		for (int i = 1; i < 999; i++)
			b[i] = b[i - 1] * 10 % n;
		
		
		//这部分内容便是举例情况,并对每种情况进行判断,考虑到是从小到大的枚举顺序,因而只要满足以下三个条件,那么break了!
		for (total = 1, aps = 0; total < 9999; total++) {
			k = 0;
			//这一部分便是 所说的对特殊数字 如:10、25、50等的快速判断
			if ((n % 10 == 0 || n % 25 == 0) && total> 11)
				k = total - 11;
			for (m = k; m < total; m++)
				for (s = 1; s < 10; s++)
					for (t = 0; t < (n % 10 ? 10 : 1); t++)
						//这里的三个判断条件很有必要说一下
						//1.二段数两端数字 即s、t 不一样 t != s
						//2.还原后的二段式必须是n的整数倍,详解见上述
						//3.ck(),还原的数字必须比输入的数字要大 不能为其本身
						if(t!=s&&(((long long)a[m]) * b[total - m] * s + a[total - m - 1] * t) % n == 0 && ck()&&
							(!aps||s<aps))
						{
							aptotal=total;
							apm=m;
							aps=s;
							apt=t;
						}
			if (aps)
				break;
		}
		for (int x = 0; x < apm + 1; x++)
			printf("%d", aps);
		for (int x = 0; x < aptotal - apm; x++)
			printf("%d", apt);
		printf("\n");
	}


  

三、总结

  这里展示一下输出结果,文中的代码也是参考自网上,文中理解主要是结合网上的一些描述后自我再次理解后的想法,看到网上许多的理解并不是非常详细,在此有必要对其进行补充说明,其中不正确或需要补充的东西,欢迎大家评论留言,谢谢!
  结果展示:
结果展示

  部分代码和理解来源于,以下网址链接:
  海岛Blog   nirvana · rebirth   平凡的昊

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值