1 #include<iostream> 2 #include<string.h> 3 #include<stdio.h> 4 using namespace std; 5 struct Node 6 { 7 int r;//余数 8 int f;//父亲节点 9 int n;//数字 10 Node(){}; 11 Node(int r1,int f1,int n1){r=r1;f=f1;n=n1;} 12 }node[10010<<2]; 13 int d[11]; 14 bool rem[11000]; 15 int n,m; 16 void print(int f) 17 { 18 if (node[f].f!=-1) print(node[f].f); 19 printf("%d",node[f].n); 20 // if ( f == -1 ) return; 21 // print( node[f].f ); 22 // printf( "%d", node[f].n ); 23 } 24 int bfs() 25 { 26 memset(rem,0,sizeof(rem)); 27 int head=1;int tail=0; 28 for(int i=1;i<=9;i++)//首位不可以为0; 29 { 30 if(d[i]) continue; 31 int r=i%n; 32 if(rem[r]) continue; 33 node[++tail]=Node(r,-1,i);rem[r]=true; 34 if (r==0) return tail; 35 } 36 while(head<=tail)//一开始等号没写,一直w 37 { 38 Node k=node[head]; 39 for(int i=0;i<10;i++) 40 { 41 if (d[i]) continue; 42 int r=(k.r*10+i)%n; 43 if (rem[r]) continue; 44 node[++tail]=Node(r,head,i); 45 rem[r]=true; 46 if (r==0) return tail; 47 } 48 head++; 49 } 50 return -1; 51 } 52 int main() 53 { 54 int cas=0; 55 while(~scanf("%d%d",&n,&m)) 56 { 57 memset(d,0,sizeof(d)); 58 for(int i=0;i<m;i++) 59 { 60 int k; 61 scanf("%d",&k); 62 d[k]=1; 63 } 64 printf("Case %d: ",++cas); 65 int ans=bfs();//找到答案则返回最后底层节点的序号 66 if (ans==-1) printf("-1\n");else {print(ans);printf("\n");} 67 } 68 return 0; 69 }
题目描述:给定一个数n,用0--9中的某几个数字组成任意正整数m,求解使m%n==0的最小的m,若m不存在,则输出-1.
方法:
1、暴力枚举n,2n,3n。。。判断是否由给定的几个数字组成,满足则输出。防止超时,限制枚举步数。
分析:使用高精度,编码较复杂;枚举步数一直TLE或W,说明这个算法行不通
2、标准方法:按数字从小到大bfs+bfs父节点存储+记忆化搜索记录简化余数计算+同余剪枝(最后的这个是剪枝的关键啊!!!)
3、方法分述:
(1)bfs()
例如:用1、2、3 组成数
拓扑结构:第一层: 1 2 3
第二层: 1 2 3 1 2 3 1 2 3
说穿了就是个满n叉树。。。。。
有了拓扑结构图,我们建模就思路清晰了。
(2)记录父节点
开始我是用queue容器+指针的写法,实在是一直错,所以我就手写了"指针",以数组下标为地址,一一对应,这点在《入门经典》的图论一部分讲的很透彻,让我又复习了一遍。
(3)记忆化搜索余数
设父节点f,余数mf;子节点c,余mc,c的个位数为cn
则 mc=(mf+cn)%n;这显然比高精度求余要优化指数级别= =
(4)同余剪枝
这个就属于数论里面的内容,描述如下:(waiting 拓展)
4、代码描述:
(1)使用了手写队列
(2)注意 head<=tail 这个条件,在整个出入队列的过程中
一个同样的数位bfs的问题:http://www.cnblogs.com/bvbook/archive/2009/02/06/1385448.html