任意给定一个正整数N,求一个最小的正整数M(M > 1),使得N*M的十进制表示形式里只含有1和0。
看了题目要求之后,我们首先想到从小到大枚举M的取值,然后再计算N*M,最后判断它们的乘积是否只含有1和0。大体思路可以用下面的伪代码实现:
1 for (M = 2; ; M++) 2 { 3 product = N * M; 4 if (hasOnlyOneAndZero(product)) 5 output N, M, product, and return; 6 }
但问题很快就出现了,什么时候应该终止循环呢?这个循环会终止吗?即使能终止,也许这个循环仍须要耗费太多的时间,比如N = 99时,M = 1122334455667789,N * M = 111111111111111111。
分析与解法
题目中直接做法显然不是一个令人满意的方法。还有没有其他的方法呢?答案是肯定的。
方法一:转化后枚举
可以对问题做一个转化:求一个最小的正整数X,X的十进制表示中只有0和1,而且X%N=0。
我们可以发现,只包含0和1的数X是这样的:1,10,11,100,101,110,111,1000,1001,1010,1011,1100,1101,1110,1111,10000……
对X循环,依次检查是否能被N整除,如果可以,就找到了最小的X,然后我们可以通过X求出M。
不难发现,如果最终的X有K位,那么我们的算法时间复杂度至少是O(2K),还能不能改进呢?
方法二:类动态规划
引入变量J=X%N,用数组bigint[J]表示除N余数为J的最小的正整数X,则bigint[0]即是我们所求。
对于任意整数X=10i+K,我们有J=X%N=(10i+K)%N=(10i%N+K%N)%N。
那么,如果bigint[J]还没有计算出来,我们可以用如下方法算得:
bigint[J]=10i+bigint[K%N]
如果bigint[J]已经存在的话,我们就不更新它的值。
为了方便,我们用一个可变长数组来表示bigint[J],如下代码中使用vector<int>来表示。
1001表示为100+103,即bigint[J]={0, 3}。
参考代码如下所示:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 typedef long long LL; 6 7 const int maxn = 1000007; 8 vector<int> bigint[maxn]; 9 10 LL tenPow(int n) 11 { 12 LL ret = 1; 13 for (int i = 0; i < n; i++) 14 ret *= 10; 15 return ret; 16 } 17 18 LL bigintToLL(vector<int> bigint) 19 { 20 int len = bigint.size(); 21 LL ret = 0; 22 for (int i = 0; i < len; i++) 23 ret += tenPow(bigint[i]); 24 return ret; 25 } 26 27 void solve(int N) 28 { 29 for (int i = 0; i < N; i++) 30 bigint[i].clear(); 31 32 bigint[1].push_back(0); 33 34 for (int i = 1, j = 10 % N; ; i++, j = (j * 10) % N) 35 { 36 bool flag = false; 37 if (bigint[j].size() == 0) 38 { 39 flag = true; 40 bigint[j].push_back(i); 41 } 42 43 for (int k = 1; k < N; k++) 44 { 45 int r = (k + j) % N; 46 if ((bigint[k].size() > 0) 47 && (i > bigint[k][bigint[k].size()-1]) 48 && (bigint[r].size() == 0)) 49 { 50 flag = true; 51 bigint[r] = bigint[k]; 52 bigint[r].push_back(i); 53 } 54 } 55 if (bigint[0].size() > 0) 56 { 57 break; 58 } 59 } 60 } 61 62 int main(int argc, char *argv[]) 63 { 64 int N; 65 while (cin >> N) 66 { 67 solve(N); 68 69 if (bigint[0].size() == 0) 70 { 71 cout << "M not exist" << endl; 72 break; 73 } 74 else 75 { 76 cout << N << " * " << bigintToLL(bigint[0]) / N 77 << " = " << bigintToLL(bigint[0]) << endl; 78 } 79 } 80 }
扩展问题
1. 对于任意的N,一定存在M,使得N*M的乘积的十制表示只有0和1吗?
结论:对于任意的N,一定存在M,使得N*M的乘积的十进制表示只有0和1。
证明:
100modN, 101modN, 102modN, ... , 10imodN, ...是一个无限的序列;
而且,这个序列只能取值0, 1, ... , N-1
因此,该序列一定是循环的序列,设周期为t,则有
10smodN = 10s+tmodN = ... = 10s+(N-1)tmodN
因此:(10s + 10s+t + ... + 10s+(N-1)t)modN = 0
所以,存在M使得 M * N = 10s + 10s+t + ... + 10s+(N-1)t 而且,这样的M并不唯一。
2. 怎样找出满足题目要求的N和M,使得N*M<216,且N+M最大?
分析:
首先,我们发现N*M < 216 = 32768,
则最大的X为11111,其次为11110
刚开始我认为,1*11111即为满足条件的N和M,最大N+M=11112,
事实上,这是不正确的,因为当N=1时,最小的M=10即可满足N*M的结果的十进制表示中只有0和1,而当取N=11111时,最小的M=10,而不是1,因为题目要求的M是大于1的,因此,这也是不满足的。
我们发现,当N越大,M越小时,满足条件的N+M的值可以更大。
最小的M=2,其次为3
当M=2时,最大的N=5555,满足N*M=11110,N+M=5557
当M=3时,最大的N=3700,满足N*M=11100,N+M=3703
显然,当M越大的时候,最大的N的取值会更小,得到的和也会比较小。
综上可知,当N=5555,M=2时,满足N*M=11110,此时的N+M最大为5557。