题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043
解题背景:
看到八数码问题,没有任何的想法,偶然在翻看以前做的题的时候发现解决过类似的一道题,不过那题是求最长的移动步骤,给你的时间有10s之多,用set和queue过了它,这题我套用当时的模板,具体解题点击这里,发现TLE了一辈子。后来直接敲了一遍lrj白书上关于八数码的代码,还是TLE,一天就这样过去了,决定看别人的思路,很快就找到Kuangbin神的代码,代码思路基本相似,只是哈希函数的差别,TLE的原因没其他,卡就卡你的哈希,搜索了多份代码,反复地提到了康托展开,百度之后决定搞懂其。
百度之后发现其只是给你介绍什么是康托展开,然后告诉你康托展开可以用代码实现,然后他就将其用代码展示予你看。||- _ -
康托展开可以看做是特殊的哈希函数,而且是不会产生冲突,固定位数的数在排列组合中得到的数按大小比较的话都有一个特定的位置,康托展开就是计算这个数在当中的排列位置,百度中你会看见:(只是想说清楚这段文字和实现代码的关系)
其举的第二个例子中,即判断1324在排列数中是第几大的数,有一个式子的模型是:X * Y!,X表示当前数到的位数其后面的数有几个比其小,Y跟此时数到的位数有关,其值是这个排列数的总的位数减去当前数到的位数,即剩下来有几位,Y!的意义是剩下的位数进行排列组合,而X表示有几个数能替代当前数到的位数当前的数,比如说刚数到第一个数时,这里表示能替代1的数有几个(比1小的数),因为你的目的是看这个排列数是第几大的数。对于1324中的1来说为0个;这时只是处理了第一个位置的位数,接下来从左往后还有需要数到的位数,Y也依次减一。最终累加的数才能得出在排列数是第几大的数。实现的代码将这些阶乘都先计算出来。
用此为哈希函数个人感觉有点像位运算通过二进制得到独一无二的数,学习ing。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<string> 3 #include<cstring> 4 #include<cstdio> 5 #define MAXN 1000003 6 #define SIZE 9 7 using namespace std; 8 9 typedef int State[SIZE]; 10 int dir[][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; 11 string dirFlag[] = {"l", "r", "u", "d"}; 12 State st[MAXN], goal = {1,2,3,4,5,6,7,8,0}; 13 string dist[MAXN]; 14 int head[MAXN], next[MAXN]; 15 16 int hash(State& s) 17 { 18 int v = 0; 19 for(int i=0; i<SIZE; ++i) v = v*10 + s[i]; 20 return v%MAXN; 21 } 22 23 int try_to_insert(int s) 24 { 25 int h = hash(st[s]); 26 int u = head[h]; 27 while(u) 28 { 29 if(memcmp(st[u], st[s], sizeof(st[s])) == 0) return 0; 30 u = next[u]; 31 } 32 next[s] = head[h]; 33 head[h] = s; 34 return 1; 35 } 36 37 int Traverse() 38 { 39 memset(head, 0, sizeof(head)); 40 int front = 1, rear = 2; 41 dist[front] = ""; 42 while(front < rear) 43 { 44 State& s = st[front]; 45 if(memcmp(goal, s, sizeof(s)) == 0) return front; 46 int z; 47 for(z=0; z<SIZE; ++z) if(!s[z]) break; 48 int x = z/3, y = z%3; 49 for(int d=0; d<4; ++d) 50 { 51 int newx = x + dir[d][0]; 52 int newy = y + dir[d][1]; 53 int newz = newx * 3 + newy; 54 if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) 55 { 56 State& t = st[rear]; 57 memcpy(&t, &s, sizeof(s)); 58 t[newz] = s[z]; 59 t[z] = s[newz]; 60 dist[rear].assign(dist[front]); 61 dist[rear].append(dirFlag[d]); 62 if(try_to_insert(rear)) rear++; 63 } 64 } 65 front++; 66 } 67 return 0; 68 } 69 70 int main() 71 { 72 freopen("F:\\test\\input.txt", "r", stdin); 73 char ch; 74 while(cin>>ch) 75 { 76 if(ch == 'x') st[1][0] = 0; 77 else st[1][0] = ch - '0'; 78 for(int i=1; i<SIZE; ++i) 79 { 80 cin>>ch; 81 if(ch == 'x') ch = '0'; 82 st[1][i] = ch - '0'; 83 } 84 int ans = Traverse(); 85 if(ans > 0) cout<<dist[ans]<<endl; 86 else cout<<"unsolvable"<<endl; 87 } 88 return 0; 89 }
WA的原因:
如果按照普通的想法来做,一般就像我刚看到题所想到,每一个case都去遍历一遍,每一次都去判段能不能走通这条路,因为是从不定的情况到确定的情况,如果是不可能走通的情况,那么每个case消耗的时间都是最大的,所以TLE是不可避免了。相反,从反过来的情况去判断,一次就能记录下来所有不能达到的情况和记录可以到达情况的步骤。傻傻的就这样想不通,这样我想起了很久之前做的一道题,每个Case都去筛素数.....隔了那么长的时间,思维却丝毫没有改变,嗒嗒
1 #include<iostream> 2 #include<string> 3 #include<cstring> 4 #include<cstdio> 5 #include<queue> 6 #define MAXN 362888 7 #define SIZE 9 8 using namespace std; 9 10 typedef int State[SIZE]; 11 int dir[][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; 12 char dirFlag[] = "rldu"; 13 14 typedef struct Status{ 15 State value; 16 }Status; 17 18 queue<Status>queuing; 19 20 string st[MAXN]; 21 bool visit[MAXN]; 22 23 int factory[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; 24 const int start = 46233; 25 int aim = 0; 26 int input[SIZE]; 27 28 int try_to_insert(int s[]) 29 { 30 int sum = 0; 31 for(int i=0; i<SIZE; ++i) 32 { 33 int cnt = 0; 34 for(int j=i+1; j<SIZE; ++j) 35 if(s[i]>s[j]) ++cnt; 36 sum += cnt*factory[SIZE-i-1]; 37 } 38 return sum; 39 } 40 41 void Traverse() 42 { 43 memset(visit, false, sizeof(visit)); 44 Status init; 45 for(int i=0; i<SIZE-1; ++i) init.value[i] = i+1; 46 init.value[SIZE-1] = 0; 47 visit[start] = true; 48 st[start] = ""; 49 queuing.push(init); 50 while(!queuing.empty()) 51 { 52 Status ss = queuing.front(); 53 State& s = ss.value; 54 queuing.pop(); 55 int z; 56 for(z=0; z<SIZE; ++z) if(!s[z]) break; 57 int x = z/3, y = z%3; 58 for(int d=0; d<4; ++d) 59 { 60 int newx = x + dir[d][0]; 61 int newy = y + dir[d][1]; 62 int newz = newx * 3 + newy; 63 if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) 64 { 65 Status tt; 66 State& t = tt.value; 67 memcpy(t, s, sizeof(s)); 68 t[newz] = s[z]; 69 t[z] = s[newz]; 70 int elem = try_to_insert(s); 71 int adr = try_to_insert(t); 72 if(!visit[adr]) 73 { 74 visit[adr] = true; 75 st[adr] = dirFlag[d] + st[elem]; 76 queuing.push(tt); 77 } 78 } 79 } 80 } 81 return;; 82 } 83 84 int main() 85 { 86 // freopen("F:\\test\\input.txt", "r", stdin); 87 char ch; 88 Traverse(); 89 while(cin>>ch) 90 { 91 if(ch == 'x') input[0] = 0; 92 else input[0] = ch - '0'; 93 for(int i=1; i<SIZE; ++i) 94 { 95 cin>>ch; 96 if(ch == 'x') ch = '0'; 97 input[i] = ch - '0'; 98 } 99 aim = try_to_insert(input); 100 if(visit[aim]) cout<<st[aim]<<endl; 101 else cout<<"unsolvable"<<endl; 102 } 103 return 0; 104 }