【POJ】1426 Find The Multiple(暴力|同余模定理|BFS)

本文介绍了一种使用BFS算法求解特定整数n的倍数m的方法,要求m仅由0和1组成。文章详细解释了解题思路,包括如何通过BFS算法结合同余模定理来优化搜索过程,并提供了两种实现方式的源代码。

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

暴力可以解决,题目要求的数据没有超出long long

#include<iostream>
using namespace std;
long long ans;
long long n;
bool dfs(long long k)
{
    if (k%n == 0)
    {
        ans = k;
        return true;
    }
    if (k >= 1000000000000000000)
        return false;
    if (dfs(k * 10 + 1))
        return true;
    if (dfs(k * 10))
        return true;
}
int main()
{
    while (cin >> n, n)
    {
        dfs(1);
        cout << ans << endl;
    }
}

或者利用同余模定理:
转载请注明出处:優YoU http://user.qzone.qq.com/289065406/blog/1303946967
大致题意:
给出一个整数n,(1 <= n <= 200)。求出任意一个它的倍数m,要求m必须只由十进制的’0’或’1’组成。

解题思路:
首先暴力枚举肯定是不可能的 1000ms 想不超时都难,而且枚举还要解决大数问题。。
要不是人家把这题放到搜索,怎么也想不到用BFS。。。

解题方法: 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+1)%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操作的过渡

由于n值有限,只是1到200的整数,因此本题也可以用打表做,通过上面的方法得到结果后,就把1~200的倍数打印出来,重新建立一个程序,直接打表就可以了。
不过打表比上面介绍的方法快不了多少

//Memory Time
//2236K  32MS 

#include<iostream>
using namespace std;

int mod[524286];  //保存每次mod n的余数
                  //由于198的余数序列是最长的
                  //经过反复二分验证,436905是能存储198余数序列的最少空间
                  //但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE

int main(int i)
{
    int n;
    while(cin>>n)
    {
        if(!n)
            break;

        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

        i--;
        int pm=0;
        while(i)
        {
            mod[pm++]=i%2;   //把*10操作转化为%2操作,逆向求倍数的每一位数字
            i/=2;
        }
        while(pm)
            cout<<mod[--pm];  //倒序输出
        cout<<endl;
    }
    return 0;
}

或者BFS(0Ms)copy from http://blog.sina.com.cn/s/blog_6635898a0100hwe3.html
题意:给出一个整数n,(1 <= n <= 200)。求出任意一个它的倍数m,要求m必须只由十进制的’0’或’1’组成。

思路:化为bfs求最短路径问题,关键有几点:
1.懂得用模拟除法运算的过程去做。
2.余数为 m (1~n-1) 的情况若出现多次,则第一次出现时所构造路径肯定比后面的情况短,根据鸽巢原理,对余数重复出现的情况进行剪枝,否则会Memory Limit Exceeded,队列的长度只限制在Max = 200。
3.构造答案的输出过程有点费心思,现在为止没想到什么好方法,只能构造出一颗二叉树,找到最后的目标节点后,再递归到根部进行输出。

源代码:(220K,0MS)

include

using namespace std;
const int Max = 201;

struct node{
int num;
int rem;
node *pre;
}; // 队列中没个结点的结构。

void print(node n){
if(n.pre == NULL)
cout << n.num;
else{
print(*n.pre);
cout << n.num;
}
} // 递归构造出答案。

int main(){
node queue[Max];
bool remain[Max];
int n;
while(cin >> n && n != 0){
memset(remain, false, sizeof(remain));
remain[1] = true;
queue[1].num = 1;
queue[1].rem = 1;
queue[1].pre = NULL;
int head = 1, tail = 2; // 队列第一个结点,队列初始化。

    bool flag = false;    //  找到答案的标志。
    node ans;    // 目标结点。
    while(!flag){
        int count = tail - head;
        while(count --){
            node now;
            for(int i = 0; i <= 1; i ++){
                now.num = i;
                now.rem = (queue[head].rem * 10 + i) % n;
                now.pre = &queue[head];
                if(now.rem == 0){
                    ans = now;
                    flag = true;
                }
                else if(!remain[now.rem]){
                    remain[now.rem] = true;
                    queue[tail++] = now;    // 注意要入列,一开始忘了wa了很久。
                }
            }
            head ++;
        }
    }
    print(ans);
    cout << endl;
}
return 0;

}

源代码:(3096K,141MS,囧)

include

include

using namespace std;
const int Max = 201;

struct data{
int num;
int m;
data *pre;
};

void print(data *p){
if(p->pre == NULL)
cout << p->num;
else{
print(p->pre);
cout << p->num;
}
}

int main(){
int n;
while(cin >> n && n != 0){
data fir, *ans;
bool flag = false;
bool hash[Max];
queue a;
memset(hash,false,sizeof(hash));

    hash[1] = true;
    fir.num = 1;
    fir.m = 1;
    fir.pre = NULL;
    a.push(fir);
    while(!a.empty() && !flag){
        int t = a.size();
        while(t --){
            data *fro = new data;
            *fro = a.front();
            a.pop();
            for(int i = 0; i <= 1; i ++){
                data *now = new data;
                now->num = i;
                now->m = (fro->m * 10 + i) % n;
                now->pre = fro;
                if(now->m == 0){
                    ans = now;
                    flag = true;
                    break;
                }
                if(!hash[now->m]){
                    hash[now->m] = true;
                    a.push(*now);
                }
            }
        }
    }
    print(ans);
   cout << endl;
}
return 0;

}

资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值