题目描述
iven a positive integer n, write a program to find out a nonzero multiple m of n whose decimal representation contains only the digits 0 and 1. You may assume that n is not greater than 200 and there is a corresponding m containing no more than 100 decimal digits.
Input
The input file may contain multiple test cases. Each line contains a value of n (1 <= n <= 200). A line containing a zero terminates the input.
Output
For each value of n in the input print a line containing the corresponding value of m. The decimal representation of m must not contain more than 100 digits. If there are multiple solutions for a given value of n, any one of them is acceptable.
Sample Input
2
6
19
0
Sample Output
10
100100100100100100
111111111111111111
这道题的意思是输入一个整数n,要求求出可以整除n的一个数,这个数必须只由0,1组成。当有多个解的时候输出任意解就可以。
解题思路
看到这道题,第一个想法就是穷举,但是穷举会超时,所以采用搜索的算法进行穷举。由于要求的输出只由0,1组成,我们就可以把这道题当做含有两个方向的搜索题。网上有用DFS和BFS两种方法,我个人更倾向于用BFS,因为使用DFS的时候,我们没有办法判断什么时候结束搜索。我看到网上有人写的DFS的代码通过搜索步骤来判断,当按照一个方向搜索的步骤超过20就结束这个方向的搜索。这个结束的步骤是怎么求出来的,没有人写,我猜测可能是试出来的。虽然DFS的代码可以AC,但我们还是使用BFS的代码来做。使用BFS来搜索,只要遇到能整除的那个数字就停止,不需要像DFS那样考虑如何停止一个方向的搜索。决定了使用BFS后,我们还需要解决一个问题,就是大数存储和计算的问题。这个可以整除的数字可能会很大,存储会溢出,计算能否整除时候会很耗费时间。有的代码使用long long来存储,提交后AC了,但是我觉得这并没有从本质上解决这个问题。在 https://blog.csdn.net/lyy289065406/article/details/6647917.这篇博客中,博主使用了同余模定理 解决大数的问题。在这里我用自己的理解再来说一遍,大家也可以看原作者的博客,写的也很清楚。
同余模定理有如下公式:
(a*b)%n = (a%n *b%n)%n
(a+b)%n = (a%n +b%n)%n
我们在使用BFS解这道题的时候,相当于从“1”开始,从0,1两个方向进行BFS。“1”后面可以添加0和1,得到了“10”,“11”。继续从0,1两个方向搜索又可以得到“100”,“101”,“110”,“111”。注意这里都是十进制的数字。我们得到数字的时候使用的是(x*10+0)和(x*10+1)这两个公式。判断这两个数字能否被n整除,我们通过(x*10+0)%n,(x*10+1)%n取余,看余数是否为0,,我们对这两个公式使用同余模定理化解,分别得到((x%n*10%n)%n+0)%n,((x%n*10%n)%n+1)%n,由于多次取模得到的结果不变,我们就可以把括号内的取模操作去掉,得到((x%n*10)+0)%n,((x%n*10)+1)%n。由于是二分的BFS搜索,整个过程可以看成一课二叉树,左子树是0,右子树是1,根节点是1。大家自己画画图就很容易想到。那么(x%n)其实就是父节点的取模结果。如果我们在BFS搜索的时候记录节点取模的结果,那么计算取模的结果就只需要很小数字的取模运算和一次乘法一次加法运算,这比大数的取模运算要快的多。当我们得到模值为0的节点时,就说明找到了整除的数字。那么这个时候怎么输出最终的结果呢?我们还是利用两个方向的BFS可以看做建立二叉树的这一特性。我们把根节点当做标号1,左子树为0,右子树为1。当找到可以整除的节点,我们只需要让节点标号对2取模,然后通过父节点回溯就可以。举个例子,输入的n等于6,我们找到标号为14的节点可以整除6,接下来开始回溯。14%2 = 0,14的父节点是14/2=7,7%2=1,7的父节点为7/2=3,3%2=1,3的父节点为1,1%2=1。将每个父节点的对2取的模倒序输出1110,1110就是最后的输出。
AC代码
#include<iostream>
using namespace std;
int m[524286];
int main()
{
int n;
while(cin>>n)
{
if(!n) break;
// cout<<n;
// int N = 10%n;
m[1] = 1;
int i = 1;
// cout<<N<<endl;
while(m[i]!=0)
{
i = i+1;
// cout<<i<<" "<<m[i/2]<<" "<<(m[i/2]+N)%n<<" "<<i%2<<" "<<((m[i/2]+N)%n+i%2)<<endl;
// m[i] = ((m[i/2]*N)%n+i%2)%n;
m[i] = (m[i/2]*10+i%2)%n;
// cout<<i<<" "<<m[i]<<endl;
}
// cout<<i<<endl;
int pm = 0;
while(i!=1)
{
m[pm] = i%2;
pm++;
i = i/2;
}
cout<<1;
pm--;
for(;pm>=0;pm--)
{
cout<<m[pm];
}
cout<<endl;
}
// cout<<10%3;
}
Tips
1、这里用数组来存储得到的模值,由于是二叉树,我们从1开始存储,这样每个节点的父节点就是i/2,可以很方便的回溯。同样做BFS的时候也只需要i++的进行下去,就是一个BFS的过程,不需要借助队列来实现。
2、m声明的大小不能太小,太小了会RE,代码里写的这个大小是https://blog.csdn.net/lyy289065406/article/details/6647917计算出来的一个下界,低于这个大小会RE。至于怎么算出来这个下界的,作者没有仔细写,就说是用二分法弄出来的,我以后有时间了仔细研究一下。
3、这个大小的m只能在主函数外申明,在主函数内申明,代码会溢出奔溃,具体原因大家自行百度,大概就是堆栈的原因,给主函数分配的空间是有限的,因此不能申请太大的空间,因此申请大数组都要在主函数外申请。