题意: 有个保险箱子是n位数字编码,当正确输入最后一位编码后就会打开(即输入任意多的数字只有最后n位数字有效)……要选择一个好的数字序列,最多只需按键10n + n - 1次就可以打开保险箱子,即要找到一个数字序列包含所有的n位数一次且仅一次。
题解:
- 为什么是按键10n + n - 1次。 不管你按键多少次,判断密码是否正确都是判断你按键的最后n位数,也就是说我们不需要真的按 n * 10n 个数字,只要一长串的数字当中出现过密码就行。
- 欧拉回路。 冥冥之中就有感觉是一道欧拉回路的问题,但为什么可以是欧拉回路?观察:每数一个数字,都会与前n - 1 个数字合并成一个n位数。这就是关键所在。输入的第i个数字和在这之前的n - 1 个数字分别是两个点,将这两个数合并是这个“操作”就是边。
代码解释
如果这个程序太难理解,可以先看我的同学的题解,我的程序是在他的基础上进行了一些化简。
- pre: 代表前n - 1 位数所表示的整数。
- deg[i]: 表示某个点的度数,实际表示某个n - 1位数可以连接
0~9
10条边,因此需要deg[] < 10. - mod: 为了保证pre始终是一个n - 1位数,每次循环将其 % mod。
- continue: 以n = 2为例,如果没有程序中的continue,答案将呈现
0102030405060708090
。到这里pre = 90 % 10 = 0
而此时deg[pre] = deg[0] = 10终止了循环,所以此处添加一个判断条件避免这种情况。 - 9: 前后补n - 1位的
0
都很好理解,但为什么要补一个9
。以n = 2为例,循环结束的答案将呈现0102030···889
,此处pre = 89 % 10 = 9
,而加上9以后又变成pre = 99 % 10 = 9
,此时的deg[pre] = deg[9] = 10,这个本应加上的9被我们自己亲手continue了。而continue的有他存在的意义,而这种极端情况也只出现在在最末尾的9,所以我们循环结束以后再补上即可。
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int n, cnt, ans[N];
void dfs(int pre){
int mod = 1, deg[N] = {0}; cnt = 0;
for(int i = 1; i < n; i++) mod *= 10;
while(deg[pre] < 10){
int w = pre * 10 + deg[pre];
deg[pre]++;
if(deg[w % mod] == 10) continue;
ans[cnt++] = deg[pre] - 1;
pre = w % mod;
}
}
int main(){
while(~scanf("%d", &n) && n){
dfs(0);
for(int i = 1; i < n; i++) printf("0");
for(int i = 0; i < cnt; i++) printf("%d", ans[i]);
printf("9");
for(int i = 1; i < n; i++) printf("0");
puts("");
}
return 0;
}