解法二:由于没有直接的数学方法能帮我们直接得到M的值,所以我们只能进行搜索。由于相对M,乘积N*M具有明显的特征,需要搜索的空间要小很多,所以我们对乘积N*M进行搜索。如果N*M的结果有K位,则要循环2^K次,我们发现K的结果能轻易超过40,所以这个运行时间还是相当长。同余运算具有等价关系,mod N = i(0<=i<N)构成了N个等价类,将正整数集进行划分。对于每个等价类,我们只需要判断其中最小的元素加上10^K是否能被N整除,这样搜索空间就从2^K次减少到(K-1)*N步,而N的值一般要远远小于M的值,但要O(N)的空间复杂度。
由于无论M还是N*M的位数都相当大,所以我们用大整数表示M和N*M。由于要N个大整数,所以N不能为大整数,即其值最好取一百万以内。
代码实现入下:
- #include<iostream>
- #include<cmath>
- #include<vector>
- using namespace std;
- bool FindNumber(int N,vector<int> *BigInt);
- int main()
- {
- int N,i;
- cout<<"Input a positive integer: ";
- cin>>N;
- vector<int> *BigInt=new vector<int>[N];//用来存放余数为0 ~ N最小数字,数字表示,如整数1001,则存为1001=10^0+10^3=(0,3),适合大数的表示
- for(i=0;i<N;i++)
- BigInt[i].clear();
- bool found=FindNumber(N,BigInt);
- if(found)
- {
- int len=BigInt[0][BigInt[0].size()-1]+1;
- char *product=new char[len+1];
- int product2=0;
- for(i=0;i<len;i++)
- product[i]='0';
- product[len]='\0';
- vector<int>::iterator iter;
- for(iter=BigInt[0].begin();iter!=BigInt[0].end();iter++)
- {
- product2=product2+pow(10,*iter);
- product[*iter]='1';
- }
- int j=len-1;
- i=0;
- while(i<j)
- {
- char tmp=product[j];
- product[j]=product[i];
- product[i]=tmp;
- i++;
- j--;
- }
- int M=product2/N;
- //product和product2都对应着乘积结果,但是product2对应着整型,乘积过大时,product2可能不会得到正确的结果;
- //product对应着乘积的字符串,可以表示大数的乘积结果,M为product2/N,即为要求的数,当product2溢出时,不是正确的结果,
- //那种情况下,需要用product/N,其中product为乘积的字符串表示,这里没有求解,读者可以自行解决。
- cout<<N<<" * "<<M<<" = "<<product<<" , "<<product2<<endl;
- delete []product;
- }
- else
- cout<<"Can not find M!"<<endl;
- delete []BigInt;
- return 0;
- }
- bool FindNumber(int N,vector<int> *BigInt)
- {
- int i,j,k;
- BigInt[1].push_back(0);
- int NoUpdate=0;
- for(i=1,j=10%N; ; i++,j=(j*10)%N)
- {
- bool flag=false;
- if(BigInt[j].size()==0)
- {
- flag=true;
- // BigInt[j]=10^i,(10^i)%N=j
- BigInt[j].clear();
- BigInt[j].push_back(i);
- }
- for(k=1;k<N;k++)
- {
- //有新的余数出现
- if((BigInt[k].size()>0)&&(i>BigInt[k][BigInt[k].size()-1])
- &&(BigInt[(k+j)%N].size()==0))
- {
- //BigInt[(k+j)%N]=10^i+BigInt[k]
- flag=true;
- BigInt[(k+j)%N]=BigInt[k];
- BigInt[(k+j)%N].push_back(i);
- }
- }
- if(flag==false)
- NoUpdate++;
- else
- NoUpdate=0;
- //如果经过一个循环节都没能对BigInt进行更新,就是无解,跳出,
- //若有N次没更新过余数信息,则存在c>=0,10^(c+1)modN,...,10^(c+N)modN中必有两个是相等的,
- //那么继续进行下去也不会再更新了
- //或者BigInt[0]!=NULL,已经找到解,也跳出.
- if(NoUpdate==N||BigInt[0].size()>0)//若有N次没更新过余数信息,则存在c>=0,10^(c+1)modN,...,10^(c+N)modN中必有两个是相等的,那么继续进行下去也不会再更新了
- break;
- }
- if(BigInt[0].size()==0)
- return false;
- else
- return true;
- }
我们可以证明对于任意的N,一定存在M,使得N*M的乘积的十进制表示只有0和1。证明过程见:http://blog.youkuaiyun.com/jcwkyl/article/details/3859155
解决这个问题首先考虑对于任意的N,是否这样的M一定存在。可以证明,M是一定存在的,而且不唯一。
简单证明:因为
这是一个无穷数列,但是数列中的每一项取值范围都在[0, N-1]之间。所以这个无穷数列中间必定存在循环节。即假设有s,t均是正整数,且s<t,有 。于是循环节长度为t-s。于是10^s = 10^t。因此有:
,所以
例如,取N=3,因为10的任何非负次方模3都为1,所以循环节周期为1.有:
给定N,求M的方法:
方法一:给定N,令M从2开始,枚举M的值直到遇到一个M使得N*M的十进制表示中只有1和0.
方法二:求出10的次方序列模N的余数序列并找出循环节。然后搜索这个余数序列,搜索的目的就是要在这个余数序列中找到一些数出来让它们的和是N的倍数。例如N=13,这个序列就是1,10,9,12,3,4然后不断循环。很明显有1+12=13,而1是10的0次方,12是10的3次方,所以这个数就是1000+1=1001,M就是1001/13=77。
方法三:因为N*M的取值就是1,10,11,100,101,110,111,......所以直接在这个空间搜索,这是对方法一的改进。搜索这个序列直到找到一个能被N整除的数,它就是N*M,然后可计算出M。例如N=3时,搜索树如下:
上图中括号内表示模3的余数。括号外表示被搜索的数。左子树表示0,右子树表示1.上图中搜索到第二层(根是第0层)时遇到111,它模3余数为0.所以N*M=111, M=111/3=37。
方法四:对方法三的改进。将方法三的搜索空间按模N余数分类,使得搜索时间和空间都由原来的指数级降到了O(N)。改进的原理:假设当前正在搜索由0,1组成的K位十进制数,这样的K位十进制数共有2^k个。假设其中有两个数X、Y,它们模N同余,那么在搜索由0、1组成的K+1位十进制数时,X和Y会被扩展出四个数:10X, 10X+1, 10Y, 10Y+1。因为X和Y同余(同余完全可以看作相等),所以10X与10Y同余,10X+1与10Y+1同余。也就是说由Y扩展出来的子树和由X扩展产生出来的子树产生完全相同的余数,如果X比Y小,那么Y肯定不是满足要求的最小的数,所以Y这棵子树可以被剪掉。这样,2^K个数按照模N余数分类,每类中只保留最小的那个数以供扩展。原来在这一层需要搜索2^K个数,现在只需要搜索O(N)个数。例如,当N=9时,第0层是1(1),
如上图所示,第2层的110,第三层的1010、1110都因为同一层有和它同余且更小的数而被剪掉。如果按照方法三搜索,第三层本来应该有8个结点,但现在只有4个结点。
方法一的源代码:
- #include <stdio.h>
- int HasOnlyOneAndZero(unsigned int n) {
- while(n) {
- if(n % 10 >= 2) return 0;
- n /= 10;
- }
- return 1;
- }
- int main() {
- int n, m;
- while(scanf("%d", &n) != EOF) {
- for(m = 1;;m++) {
- if(HasOnlyOneAndZero(n*m)) {
- printf("n = %d, m = %d, n*m = %d/n", n, m, n*m);
- break;
- }
- }
- }
- return 0;
- }
方法三的源代码:
- // 解法三(1):广度优先搜索
- #define _CRT_SECURE_NO_WARNINGS 1
- #include <cstdio>
- #include <queue>
- using namespace std;
- int main() {
- int N;
- while(scanf("%d", &N) != EOF) {
- queue<int> q;
- q.push(1);
- while(!q.empty()) {
- int t = q.front();
- q.pop();
- if(t % N == 0) {
- printf("n = %d, m = %d, n*m = %d/n", N, t/N, t);
- break;
- }
- q.push(t * 10);
- q.push(t * 10 + 1);
- }
- }
- return 0;
- }
方法四源代码:
- // 解法四:将搜索空间分过类的广度搜索,这样空间占用是O(N)而不是
- // 指数级。分类的原则是按照模N的余数分类
- #define _CRT_SECURE_NO_WARNINGS 1
- #include <cstdio>
- #include <bitset>
- #include <vector>
- #include <queue>
- using namespace std;
- struct QNode {
- int v, r; // v is value, r is remainder
- QNode(int vv, int rr): v(vv), r(rr) {}
- QNode(): v(0), r(0) {}
- };
- int main() {
- int N;
- while(scanf("%d", &N) != EOF) {
- //bitset<N> bn;
- queue<QNode> q;
- q.push(QNode(1, 1));
- while(!q.empty()) {
- //bn.reset();
- vector<bool> bn(N, false);
- int s = q.size();
- while(s--) {
- QNode t = q.front();
- if(t.r == 0) {
- printf("n = %d, m = %d, n*m = %d/n", N, t.v/N, t.v);
- goto ok;
- }
- q.pop();
- if(!bn[t.r * 10 % N]) {
- bn[t.r * 10 % N] = true;
- q.push(QNode(t.v * 10, t.r * 10 % N));
- }
- if(!bn[(t.r * 10 + 1) % N]) {
- bn[(t.r * 10 + 1) % N] = true;
- q.push(QNode(t.v * 10 + 1, (t.r * 10 + 1) % N));
- }
- }
- }
- ok:;
- }
- return 0;
- }