题意:有个保险箱子是n位数字编码,当正确输入最后一位编码后就会打开(即输入任意多的数字只有最后n位数字有效)……要选择一个好的数字序列,最多只需按键10n+n-1次就可以打开保险箱子,即要找到一个数字序列包含所有的n位数一次且仅一次。序列要为字典序。
题解:首先明白为什么是最多只需按键10n+n-1次。n位数有10n 种编码方案,要一个数字序列包含10n 组n位数且序列最短,只可能是每组数出现一次且仅一次,且前一组数的后n-1位与后一组数的前n-1位相同,10n组数各取一位,再加上最后一组数的n-1位,总共10n +n-1位,如下所示:
第一组:d1 d2 d3…dn
第二组: d2 d3 d4…d(n+1)
…
第10n组:…d(10n+n-3) d(10n+n-2) d(10n+n-1)
然后,把n-1位看成一个图中顶点,将n-1位后加一个数字(0~9)的序列看成一条边,共10n-1个顶点,10n条边,且每条边都不相同,所以这10n组不同的n位数对应图中的一个 欧拉通路。(怎么想过来的呢,你仔细看题目都提示了:题中说,保险箱始终处于10n-1种内部状态之一,假如正确编码为4567,”开锁状态“就是456,如果再输入7就开锁了,如果输入8就切换到新的状态568,然后就想转化到图上来了,把内部状态(n-1位的序列)看成顶点咯。要求解的序列最短,就是从一个顶点出发不重复地遍历所有边到达终点,这不就是赤裸裸的欧拉回路么0.0+)
注意,该题直接用递归的方法会导致栈溢出,所以要显式地用栈来实现。存储结果时优先存较大值,这样对结果栈逆序输出时就是按字典序排列啦。
代码实现:
#include<cstdio> const int N=1e5; int node[N],stack[10*N]; char ans[10*N];//结果栈 int s,a; int m; void Search(int v){//将当前顶点延伸 int w; while(node[v]<10){//可以在v(n-1位的序列)后加0~9构成10条边 w=10*v+node[v]; node[v]++; stack[s++]=w; v=w%m; } } int main(){ int n,i,w; while(scanf("%d",&n)&&n!=0){ if(n==1){ printf("0123456789\n"); continue; } s=a=w=0; m=1; for(i=0;i<n-1;++i) m*=10; for(i=0;i<m;++i) node[i]=0; Search(0); while(s){ w=stack[--s]; ans[a++]=w%10+‘0‘; Search(w/10); } for(i=1;i<n;++i) printf("0"); while(a) printf("%c",ans[--a]); printf("\n"); } return 0; }
时间: 2024-10-11 23:12:16