前言
发现一道好题目。题目广搜,需要绝妙的数学证明与剪枝,思维含量较大,搜索玩出花来。
若在比赛中肯定想不出正解(话说这篇题解也不是最优解)。我的数学太弱了……抽屉原理都不知道……
看题解吧: )
题目
给定一个自然数N,找出一个M,使得M>0且M是N的倍数,并且M的10进制表示只包含0或1。求最小的M。
例如:N = 4,M = 100。
输入
输入1个数N。(1≤N≤106)
输出
输出符合条件的最小的M。
输入样例
4
输出样例
100
题解
首先对M存在进行证明。
考虑数列:1,11,111,…,11…11(N个1)。由抽屉原理知其中要么有数能被N整除,要么有两个数除N同余。若有数能整除,命题显然成立;若有两数除N同余,用大数减去小数,得到的数形如11…10…00,,能被N整除,命题也成立。
接着搜索构造M。最开始的数为1,每次考虑在原数的末尾加上0或1,按照此法进行搜索,当构造的数%N==0时,即为答案。此处搜索要用广搜,因为广搜可以保证搜到的第一个数一定是最小的。
显然,如此搜索需要剪枝。这里给出剪枝的方法是:若有两个构造得到的数x,y,不妨设x<y。如果x%N=y%N,那么y不必继续拓展。
证明。设x=k1n+r,y=k2n+r。x拓展得到的数x1=10k1n+10r,x2=10k1n+10r+1,y拓展得到的数为y1=10k2n+10r,y2=10k2n+10r+1。可以发现,x1%n=y1%n,x2%n=y2%n。于是得到,x拓展到答案的步数与y拓展到答案的步数相等。那么y显然不可能成为最佳答案,我们只需要拓展较小的数x即可。
具体实现时,只需不断标记x%n,之后若拓展得到的y%n已经被标记,那么continue。
但又有另一个问题:M可能极大,超出long long的范围,高精度又太慢,那该如何记录构造出的数呢?
注意到,数x到答案的步数只与x除n的余数r有关(可以从上文x拓展得到的x1与x2除n的余数证明)。再看上文剪枝的方法,发现:每个除n的余数只对应单独一个构造的数。那么我们可以以余数r为下标,记录对应的数的构造路径(例如101由10拓展得,那么101的r指向10的r)。输出时一路往前回指,递归输出即可。
此解针对的数据规模为N≤106,此题的进阶版N≤109需要更强大的算法,我也不会……
Code
#include<cstdio>
#include<queue>
using namespace std;
const int N = 1e6 + 10;
struct node {
int bit, pre;
} path[N];
int n;
bool vis[N];
queue<int> q;
void bfs() {
int x, y;
path[1] = (node){1, -1};
vis[1] = 1;
q.push(1);
while (!q.empty()) {
x = q.front(); q.pop();
if (x == 0) return ;
for (int i = 0; i <= 1; i++) {
y = (x * 10 + i) % n;
if (!vis[y]) {
path[y] = (node){i, x};
vis[y] = 1;
q.push(y);
}
}
}
}
void Print(int x) {
if (path[x].pre != -1) Print(path[x].pre);
printf("%d", path[x].bit);
}
int main() {
scanf("%d", &n);
bfs();
Print(0);
printf("\n");
return 0;
}