问题描述:
任意给定一个正整数N,求一个最小的正整数M(M>1),使得N*M的十进制表示形式里只含有1和0.
分析与解法一:
最简单的方法就是从2开始,依次遍历M,直到找N*M中只含1和0位置,这种方法很不好,例如N=99时,M=1122334455667789,N*M=111111111111111111.这个所花费的时间显然是我们不希望看到的。
分析与解法二:
对于遍历M,我们不如直接遍历M*N,因为M*N比较有特征性,只含0与1,因此,要遍历一个K为,只含0和1的数,我们仅仅需要的遍历的次数为2K,遍历从1开始,遍历的学列为1,10,11,100,101….注意,可以发现,这其实一颗二叉树,10和11可以看做是1的左右儿子,100和101也看成是10的左右儿子,…,照这个规律,其生成的二叉树如下:
每个节点下面的数字为除3的余数。
这里可以发现一个规律,就是如果一个节点的余数与另一个节点的余数相同,那么他们的儿子节点余数也一样,发现这一点就可以对上面的二叉树剪枝。之所以是这样,是因为同余定理。
同余定理:
如果a%c = d; b%c = d,那么(a+x)%c == (b+x)%c == d。
因为a%c == b%c == d;
所以a = a1*c+d,b = b1*c+d;
进而a+x = a1*c+d+x,b+x = b1*c+d+x;
所以(a+x)%c == (b+x)%c == x+d;
算法思路:
总体来说采用的深度优先的策略,遍历上图中的某一层的某一个节点,只要除N的余数不为0,则入栈(入栈同时根据同余定理进行剪枝,如果该节点与其他遍历过的节点同余,该节点将不会入栈),如果余数为0则找到了M,程序退出。如果没有发现有效M,则依次弹出栈,进行X*10和X*10+1扩展,然后重复上述步骤。其中,剪枝通过采用一个N维行向量进行记录是否出现同余的情况实现。
1. 初始化余数数组model[N],全为0;
2. M*N = 1,将1进栈;计算1%N,如果1%N==0,算法结束,否则将model[1]设置为1;
3. M*N = M*N*10,计算M*N%N,如果为0,算法结束,否则,如果model[M*N%N]==1,跳到4,否则M*N入栈;
4. M*N = M*N*10+1,计算M*N%N,如果为0,算法结束,否则,如果model[M*N%N]==1,跳到5,否则M*N入栈;
5. 弹出栈元素,跳到3;
在进行这个题目的时候,在调试的时候发现有个问题,就是大整数的溢出问题,我的程序只能处理64位的ULL。下面是代码:
#include <iostream>
#include <climits>
#include "Stack.h"
#include "Queue.h"
using namespace std;
int main()
{
cout << "Pleaseinput N:";
unsigned long long N;
cin >> N;
bool *model= new bool[N];
for(int i = 0; i < N; i++)
model[i] = 0;
unsigned long long count = 0;
Queue<unsignedlong long>s;
unsigned long long X = 1;
long long m = X%N;
s.EnQueue(X);
model[m] = true;
while(!s.QueueIsEmpty())
{
count++;
unsignedlong long tmp;
s.DeQueue(tmp);
long long tmpmode;
if(tmp< ULLONG_MAX/10)
{
unsignedlong long tmpX;
tmpX = tmp*10;
tmpmode = tmpX%N;
if(tmpmode== 0)
{
X = tmpX;
break;
}
else
{
if(!model[tmpmode])
{
model[tmpmode] = true;
s.EnQueue(tmpX);
}
}
}
if(tmp< LLONG_MAX/10)
{
unsignedlong long tmpX;
tmpX = tmp*10+1;
tmpmode = tmpX%N;
if(tmpmode== 0)
{
X= tmpX;
break;
}
else
{
if(!model[tmpmode])
{
model[tmpmode] = true;
s.EnQueue(tmpX);
}
}
}
}
cout << "TheM is " << X/N << endl;
cout << "count= " << count << endl;
system("pause");
return 0;
}
运行结果: