...还是要加油啊...
通常,我们用bfs来解决最短路径问题。一般与图联合出现。但是,bfs,dfs也可以用来解决数学中的问题。
例如下面这题:
问题 L: 寻找倍数
时间限制: 2.000 Sec 内存限制: 512 MB
提交 状态
题目描述
给定一个正整数n,编写一个程序来找出n的一个非零倍数m,其十进制表示仅包含数字0和1。你可以假设n不大于998,并且存在一个相应的m,其十进制位数不超过100位。
输入
输入包含多个测试用例。每行包含一个n的值(1 <= n <= 998),遇到0输入结束。
输出
对于输入中的每个n值,打印一行包含对应的最小的m的值。
样例输入 Copy
2 6 19 0
样例输出 Copy
10 1110 11001
这题我一开始看,就以为是个抽象的数论问题,就给跳了...(下次再也不了www)不过,细想这题的状态,每个结果都只能由 0 1 构成,那不就是,每个数在之前的前提下,×10或×10+1 嘛?这样一来,前后推导关系就出来了,自然就好想了。
但还有一个问题,就是这个“不超过100位”。我们肯定不能用long long来存储每次的结果,那自然要用到字符串。但高精度除法又太麻烦...这时我们注意到,这题只要判断 是否能整除 即可,那么,余数的性质能很好的满足这一点。例如,求一个10000位的字符串a整除数b的余数,程序可以写成:
string a;
int b;
int t=0;
for(auto i:a){
t=(t+(i-'0')*10)%b;
}
//t即为要求的余数。
那么,我们就可以将程序写成:
#include<iostream>
#include<queue>
using namespace std;
int n;
void bfs(){
queue<string>a;
a.push("1");
while(!a.empty()){
string num=a.front();a.pop();
int t=0;
for(auto g:num){
t=(t*10+g-'0')%n;
}
if(t==0){
cout<<num<<'\n';
return ;
}else{
a.push(num+'0');
a.push(num+'1');
}
}
}
int main()
{
while(scanf("%d",&n),n){
bfs();
}
return 0;
}
这样写自然是可以过的,不过如果不开o3优化,就会TLE。那么,如何优化呢?
我们平常写bfs的时候,其实都会判断此时用到的数是否走过,走过就不走。那么这题也是这个道理。可以用一个 unordered_set来存储每个用过的余数,用其count()函数来判断此时的字符串是否重复。
代码如下:
#include<iostream>
#include<queue>
#include<unordered_set>
using namespace std;
int n;
void bfs(){
queue<string>a;
unordered_set<int>u;
a.push("1");
while(!a.empty()){
string num=a.front();a.pop();
int t=0;
for(auto g:num){
t=(t*10+g-'0')%n;
}
if(t==0){
cout<<num<<'\n';
return ;
}else if(!u.count(t)){
a.push(num+'0');
a.push(num+'1');
u.insert(t);
}
}
}
int main()
{
while(scanf("%d",&n),n){
bfs();
}
return 0;
}
其中,为什么要判断每个 余数 是否用过呢?因为两个余数相同的数 经过相同的变换 余数仍相同。
下面粘自AI:
如果两个数在被m除时的余数相同,那么无论我们对这两个数进行何种相同的运算或变化,它们在被m除时的余数仍然相同。这表明同余关系在数学运算中具有稳定性,确保了余数相同的两个数经历相同变化后余数依然相同。
综上所述,其实我觉得本题的核心点是想到 每个状态可以再往下分为两个状态 ,进而想到bfs算法。