BFS POJ1426 Find The Multiple

本文介绍了一道编程题目,要求找出一个只包含0和1的正整数m,使其能够被给定的正整数n整除。文章详细阐述了如何利用BFS算法解决该问题,并给出了两种实现方案,一种是直接的BFS搜索,另一种则是通过优化减少大数处理带来的复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Find The Multiple
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 22471 Accepted: 9236 Special Judge
Description
Given 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
Source
Dhaka 2002

题意:给一个不大于200的正整数n,求某个只由数字0和1组成的正整数m,使n整除m。
证明1:任意一个正整数N,必存在一个只由数字0和1组成的数M,使得N整除M。
在数1,11,111,1111 … … 111…111(N个1)中:
(1)存在一个数a,使得a mod n = 0。那么10*a mod n = 0 ,且10*a只由数字0和1组成。
(2)不存在数能整除N,但是总存在两个数p,q,其除N的余数相同(见证明2)。设p=r1*N+s,q=r2*N+s。那么,b=p-q=(r1-r2)*N是N的倍数,且只由0和1组成。
证毕。
证明2:1,11,111,1111,… … ,111…11(N个1)分别处以N,得到N个余数,且所有余数中最多只有N-1个不同的余数(1到N-1,因为所有数都不能被N整除,所以余数中没有0),由鸽巢原理知,必有两个余数相同。
思路:简单的BFS题,朴素搜索,从1开始,10,11,101,100,110,111 … 运用队列。每次搜索到队首数a,就将a*10和a*10+1入队列,直到找到答案。

//5044K 407MS
#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    int n;
    long long tmp_Front;
    while(scanf("%d",&n)!=EOF&&n){
        queue<long long> q;
        q.push(1);
        while(!q.empty()){
            tmp_Front=q.front();
            q.pop();
            if(tmp_Front%n==0){
                printf("%lld\n",tmp_Front);
                break;
            }
            q.push(tmp_Front*10);
            q.push(tmp_Front*10+1);
        }
    }
    return 0;
}

//下面是大神的方法,效率是朴素搜索方法的10倍,//http://m.blog.youkuaiyun.com/blog/lyy289065406/6647917
解题方法: BFS+同余模定理
首先说说朴素的不剪枝搜索方法:
我以n=6为例
首先十进制数,开头第一个数字(最高位)一定不能为0,即最高位必为1

设6的 ”01十进制倍数” 为k,那么必有k%6 = 0
现在就是要用BFS求k值
1、先搜索k的最高位,最高位必为1,则此时k=1,但1%6 =1 != 0
因此k=1不是所求,存储余数 1
2、搜索下一位,下一位可能为0,即 k*10+0,此时k=10,那么k%6=4
可能为1,即 k*10+1,此时k=11,那么k%6=5
由于余数均不为0,即k=10与k=11均不是所求
3、继续搜索第三位,此时有四种可能了:
对于k=10,下一位可能为0,即 k*10+0,此时k=100,那么k%6=4
下一位可能为1,即 k*10+1,此时k=101,那么k%6=5
对于k=11,下一位可能为0,即 k*10+0,此时k=110,那么k%6=2
下一位可能为1,即 k*10+1,此时k=111,那么k%6=3
由于余数均不为0,即k=100,k=101,k=110,k=111均不是所求
4、继续搜索第四位,此时有八种可能了:
对于k=100,下一位可能为0,即 k*10+0,此时k=1000,那么k%6=4
下一位可能为1,即 k*10+1,此时k=1001,那么k%6=5
对于k=101,下一位可能为0,即 k*10+0,此时k=1010,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1011,那么k%6=3
对于k=110,下一位可能为0,即 k*10+0,此时k=1100,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1101,那么k%6=3
对于k=111,下一位可能为0,即 k*10+0,此时k=1110,那么k%6=0
下一位可能为1,即 k*10+1,此时k=1111,那么k%6=1
我们发现k=1110时,k%6=0,即1110就是所求的倍数
从上面的演绎不难发现,用BFS是搜索 当前位数字 (除最高位固定为1),因为每一位都只有0或1两种选择,换而言之是一个双入口BFS
本题难点在于搜索之后的处理:对余数的处理,对大数的处理,余数与所求倍数间的关系
接下来说说处理大数问题和剪枝的方法:
首先我们简单回顾一下 朴素搜索 法:

n=6
1%6=1  (k=1){  
 (1*10+0)%6=4  (k=10){
    (10*10+0)%6=4   (k=100) {
        (100*10+0)%6=4  (k=1000)
        (100*10+1)%6=5  (k=1001)
    }
    (10*10+1)%6=5  (k=101){
        (101*10+0)%6=2  (k=1010)
        (101*10+1)%6=3  (k=1011)
    }
}
   (1*10+1)%6=5  (k=11){
    (11*10+0)%6=2   (k=110){
        (110*10+0)%6=2  (k=1100)
        (110*10+1)%6=3  (k=1101)
    }
    (11*10+1)%6=3   (k=111){
        (111*10+0)%6=0  (k=1110)   有解
        (111*10+1)%6=1  (k=1111)  由于前面有解,这个余数不存储
    }
}
}

从上面可以看出余数的存数顺序(逐层存储):
用数组mod[]存储余数,其中mod[0]不使用,由mod[1]开始
那么mod中的余数依次为: 1 4 5 4 5 2 3 4 5 2 3 2 3 0 共14个
即说明我们得到 余数0 之前,做了14步*10的操作,那么当n值足够大的时候,是很容易出现k为大数的情况(事实上我做过统计,200以内的n,有18个n对应的k值为大数
那么我们再用int去存储k就显得不怎么明智了。
为了处理所有情况,我们自然会想到 是不是应该要用int[]去存储k的每一位?
而又由于k是一个01序列,那能不能把 *10得到k每一位的问题 转化为模2的操作得到k的每一位(0或1) 呢?
答案是可以的
首先我们利用 同余模定理 对得到余数的方式进行一个优化
(a*b)%n = (a%n *b%n)%n
(a+b)%n = (a%n +b%n)%n
随便抽取上面一条式子为例
前一步 (11*10+0)%6=2 即k=110 , k%6=2
当前步 (110*10+1)%6=2
由同余模定理 (110*10+1)%6 = ((110*10)%6+1%6 )%6 = ((110%6 * 10%6)%6 +1 )%6
不难发现下划线部分110%6等于 (11*10+0)%6 = 2
所以当前步(110*10+1)%6可以转变为 (2*10+1)%6=2

很显然地,这种处理把k=110 等价于 k=2
即用 前一步操作得到的余数 代替 当前步的k值
而n在200的范围内, 余数值不可能超过3位数, 这就解决了 大数的问题
通过这种处理手法,我们只需在BFS时顺手存储一个 余数数组mod[] ,就能通过mod[i-1]得到mod[i] ,直到mod[i]==0 时结束,大大减少了运算时间
前面已经提到,n=6时,求余操作进行了14次,对应地,BFS时*10的操作也进行了14次。
令i=14,通过观察发现,i%2恰好就是 6 的倍数的最低位数字
i/2 再令 i%2 ,恰好就是 6 的倍数的 次低位数字。。。
循环这个操作,直到i=0,就能得到 6的 01倍数(一个01队列),倒序输出就是所求
这样就完成了 *10操作到 %2操作的过渡

//2748K 47MS
#include<iostream>
#include<cstdio>
using namespace std;
int mod[524286];  //保存每次mod n的余数
                  //由于198的余数序列是最长的
                  //经过反复二分验证,436905是能存储198余数序列的最少空间
                  //但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE
int main(int i){
     freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    int n;
    while(cin>>n&&n){
        mod[1]=1%n;  //初始化,n倍数的最高位必是1
        for(i=2;mod[i-1]!=0;i++)  //利用同余模定理,从前一步的余数mod[i/2]得到下一步的余数mod[i]
            mod[i]=(mod[i/2]*10+i%2)%n;
                     //mod[i/2]*10+i%2模拟了BFS的双入口搜索
                     //当i为偶数时,+0,即取当前位数字为0  。为奇数时,则+1,即取当前位数字为1
        //for(i=1;mod[i]!=0;i++)cout<<mod[i]<<" ";cout<<endl;
        i--;
        int pm=0;
        while(i){
            mod[pm++]=i%2;   //把*10操作转化为%2操作,逆向求倍数的每一位数字
            i/=2;
        }
        while(pm) cout<<mod[--pm];  //倒序输出
        cout<<endl;
    }
    return 0;
}

注意:第二个程序开了50万的内存,2700k的内存,第一个程序没有开数组,但是内存达到了5000k。这是因为第一个程序的队列运行时最大存了5000k的数据,而且毕竟是long long,比int 多4字节,而第二个程序虽然开了50万的内存,但实际运行时可能没用到那么多的内存,数组开成100万一样的2700k。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值