[kuangbin带你飞]专题二 搜索进阶
[kuangbin带你飞]专题二 搜索进阶
这一题就是经典八数码。算法难点在于:
1.判重:map,hash,cantor康托展开
2.搜索:bfs dbfs A*
我是直接暴力预处理,从12345678X开始进行所有情况处理,然后结果反序输出。节点用int存储9位数的序列。
#include <iostream> #include <queue> #include <map> #include <algorithm> using namespace std; map<int,string> ans; int n[10]; struct node{ int num,nine; node(int nn,int nni):num(nn),nine(nni) {}; }; int change(int a,int b,int c){ int u = c/n[a]%10, v = c/n[b]%10; //cout<<a<<" "<<b<<" "<<c<<endl; //cout<<u<<" "<<v<<" "<<u*n[a]<<" "<<v*n[b]<<" "<<v*n[a]<<" "<<u*n[b]<<endl; return c - u*n[a] - v*n[b] + v*n[a] + u*n[b]; } void init(){ n[8] = 1;for(int i=7;i>=0;i--) n[i] = n[i+1]*10; //for(int i=0;i<9;i++) cout<<n[i]<<endl; queue<node> q; ans[123456789] = ""; q.push(node(123456789,8)); while(!q.empty()){ node p = q.front();q.pop(); int num = p.num , nine = p.nine; int tnt; //--------------------------------------------- switch(nine){ case 0: tnt = change(0,1,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,1)); } tnt = change(0,3,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,3)); } break; case 1: tnt = change(1,0,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,0)); } tnt = change(1,2,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,2)); } tnt = change(1,4,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,4)); } break; case 2: tnt = change(2,1,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,1)); } tnt = change(2,5,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,5)); } break; case 3: tnt = change(3,0,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,0)); } tnt = change(3,4,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,4)); } tnt = change(3,6,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,6)); } break; case 4: tnt = change(4,1,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,1)); } tnt = change(4,3,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,3)); } tnt = change(4,5,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,5)); } tnt = change(4,7,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,7)); } break; case 5: tnt = change(5,2,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,2)); } tnt = change(5,4,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,4)); } tnt = change(5,8,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "u"; q.push(node(tnt,8)); } break; case 6: tnt = change(6,3,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,3)); } tnt = change(6,7,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,7)); } break; case 7: tnt = change(7,6,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,6)); } tnt = change(7,4,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,4)); } tnt = change(7,8,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "l"; q.push(node(tnt,8)); } break; case 8: tnt = change(8,5,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "d"; q.push(node(tnt,5)); } tnt = change(8,7,num); if(ans.count(tnt)==0){ ans[tnt] = ans[num] + "r"; q.push(node(tnt,7)); } break; default:cout<<nine<<endl; } //--------------------------------------------- } } int main(){ init(); char _; while(cin>>_){ int cnt=0; if(_=='x') _='9'; cnt+=(_-'0')*n[0]; for(int i=1;i<9;i++) { cin>>_; if(_=='x') _='9'; cnt+=(_-'0')*n[i]; } //cout<<cnt<<endl; //cout<<ans[cnt]<<endl; if(ans.count(cnt)==0) puts("unsolvable"); else{ string strng = ans[cnt]; //cout<<strng<<endl; for(int i=strng.length()-1;i>=0;i--){ cout<<strng[i]; }cout<<endl; } } return 0; }
这里是要求是给出 起点和终点序列 然后得出最短操作 而且相同长度要求最小字典顺序(重点,我这里wrong了很多次 看题目才知道)
第一遍:就是A* + map 尝试 然后 timelimit。
第二遍:看其他人代码,有两种,一种就是设立limit的dfs;这几天了解一下 这是IDA*算法。迭代加深的A*算法。
f(x) = 层数 + 与目标序列的相同字符曼哈顿距离之和;然后从第一个输入的序列的f(x)为limit,然后limit逐步以最小量增加为dextd直到找到答案位置。
这里我直接给出大牛代码的链接。https://www.cnblogs.com/DOLFAMINGO/p/7538577.html
#include <iostream> #include <cmath> #include <algorithm> #include <cstdio> using namespace std; //int的取值范围为: -2^31——2^31-1,即-2147483648——2147483647 const int INF = 2e9; int Y[10],M[10],op=1,nextd; char C[1234]; int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1}, {6,-1,4,0},{7,3,5,1},{8,4,-1,2}, {-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}}; char d[5] = "dlru"; int forLimit(int *a){ int ans = 0; for(int i=0;i<9;i++) if(a[i]!=0) ans += abs(i/3 - M[a[i]]/3) + abs(i%3 - M[a[i]]%3); return ans; } bool IDAstar(int k,int step,int pre,int limit){ int h = forLimit(Y); if(h==0){ printf("Case %d: %d\n",op++,step); C[step] = '\0';puts(C); return true; } if(h+step>limit){ nextd = min(nextd,h+step); return false; } for(int i=0;i<4;i++){ int pos = changeId[k][i]; if(pos==-1||i+pre==3) continue; C[step] = d[i]; swap(Y[k],Y[pos]); if(IDAstar(pos,step+1,i,limit)) return true; swap(Y[k],Y[pos]); } return false; } int main() { int _;scanf("%d",&_); while(_--){ char a[10],b[10]; int k; scanf("%s%s",a,b); for(int i=0;i<9;i++) if(a[i]=='X') Y[i] = 0,k = i; else Y[i] = a[i] - '0'; for(int i=0;i<9;i++) if(b[i]=='X') M[0] = i; else M[b[i]-'0'] = i; //for(int i=0;i<9;i++) cout<<Y[i];cout<<endl; //for(int i=0;i<9;i++) cout<<M[i];cout<<endl; //--------------------------- for(int limit = forLimit(Y);;limit = nextd){ nextd=INF; if(IDAstar(k,0,INF,limit)) break; } } return 0; }
自己码了一遍,发现易错点在于 1.nextd = INF 需要每次循环 重新定义为INF 2.f(x) = h(x) + g(x). 这里构造启发函数,先试着用简单的不同字符数来运算,结果跑不出来,后改为曼哈顿距离,然后试着连着X字符也计算,发现wrong,这里这样的确不是最优,但是举不出反例,所以也不知道为什么?最后就用作者一样,不算X的曼哈顿距离,然后就通过了。3.最后最重要还是减枝,避免死循环。
另一种就是预处理 ,这里很巧妙 我想了一早上 想通了;重点 x 是操作单位不能动,其他字符是可替换的。
举例子:645X312 ---->456X123 的操作 和 123X456---->231X564的操作一样 为什么?因为这只是简单字符替换 ,这种替换并不影响结果。甚至645X312可以替换成qweXyui,不影响的。
然后就是12345678X,1234567X8....9种X在不同位置的预处理,然后通过给出起点X位置进行得出结果。
重点 要 {dlru}这样进行遍历 因为需要最小字典序列
#include <iostream> #include <queue> #include <cstring> #include <string> using namespace std; const int M = 400000; int vis[9][M]; int pre[9][M]; //----------------------------------------------------------- void show(int *a){ for(int i=0;i<9;i++) cout<<a[i];cout<<endl; } //------------------------------------------------------------ int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1}, {6,-1,4,0},{7,3,5,1},{8,4,-1,2}, {-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}}; char d[5] = {'d','l','r','u'}; //康托展开====================================================== const int fk[10] = {1,1,2,6,24,120,720,5040,40320,362880}; int cantor(int *a){ int ans = 0; for(int i=0;i<8;i++){ int small = 0; for(int j=i+1;j<9;j++) if(a[j]<a[i]) small++; ans += small*fk[8-i]; } return ans; } //---------------------------------------------------------- struct node{ int num[9],zero,hashcode; }; void bfs(int x){ node now; for(int i=0,j=1;i<9;i++) if(i==x) now.num[i] = 0; else now.num[i] = j++; now.zero=x; now.hashcode = cantor(now.num); queue<node> q; q.push(now); vis[x][now.hashcode] = 1; while(!q.empty()){ node p = q.front();q.pop(); int zero = p.zero,hashcode = p.hashcode; for(int i=0;i<4;i++){ if(changeId[zero][i] == -1) continue; node temp = p; temp.zero = changeId[zero][i]; swap(temp.num[zero],temp.num[temp.zero]); temp.hashcode = cantor(temp.num); if(vis[x][temp.hashcode]==-1){ vis[x][temp.hashcode] = i; pre[x][temp.hashcode] = hashcode; q.push(temp); } } } } //预处理================================================= void init(){ memset(vis,-1,sizeof vis); memset(pre,-1,sizeof pre); for(int i=0;i<9;i++) bfs(i); } //--------------------------------------------------------- int main(){ init(); int _,op=1; scanf("%d",&_); while(_--){ //---------------------- int k; char a[10],b[10]; int c[10],u[10]; scanf("%s%s",a,b); //printf("%s\n%s\n",a,b); //进行符号兑换:c数组为兑换数组,得到兑换后的目标数组和X位置======== for(int i=0,j=1;i<9;i++) if(a[i]=='X') k=i; else c[a[i]-'0'] = j++; for(int i=0;i<9;i++) if(b[i]=='X') u[i] = 0; else u[i] = c[b[i]-'0']; //show(u); //=============================== int can = cantor(u); string ans = ""; while(can != -1){ ans = d[vis[k][can]] + ans; can = pre[k][can]; } printf("Case %d: %d\n",op++,ans.length()-1); cout<<ans.substr(1)<<endl; } return 0; }
算法:简单dfs,题目本身给出的每个星球连接星球就是从小到大,所以不用再排序。试着简单写就过了。
#include <iostream> #include <cstring> using namespace std; int W[21][3],op=1; bool vis[21]; int C[22]; void dfs(int m,int k,int step){ if(step==21&&m==k){ cout<<op++<<": "; for(int i=1;i<=step;i++) cout<<" "<<C[i]; cout<<endl; }else{ for(int i=0;i<3;i++){ if(vis[W[m][i]]) continue; vis[W[m][i]] = true; C[step+1] = W[m][i]; dfs(W[m][i],k,step+1); vis[W[m][i]] = false; } } } int main() { for(int i=1;i<=20;i++) cin>>W[i][0]>>W[i][1]>>W[i][2]; memset(vis,false,sizeof vis); int m; while(cin>>m){ if(m==0) break; C[1] = m; dfs(m,m,1); } return 0; }
拓展思考:每个星球固定连着其他星球,且互通,不是单向;然后球给定起点的循环路径;
其实想想真正含有信息的路径是固定的,因为你用不同路径去绕一圈,点顺序不同的路径是固定,这些路上的点都能绕自己一圈。所以你只要求出关键的路径收集就好了。
然后这些路径数量就只有答案总数一半,因为路径反转又是另个一个答案。类似你正向走和反向走。
致命的是最小字典输出。这里纠结了很久。这里想了一下用字符串存储,然后每次输入起点都从这些字符串中,滚动变为起点打头的字符串,然后进行存储排序就行了。
记得几年前做过这个代码,但是忘记了,也想等到刷到字符串题目时回来再想想现在有什么更加方便的存储方式,或者算法,进行学习记录。
万万没想到这题简单dfs就过了。。。。。
D - EscapeHDU - 3533
算法:A* BFS 函数 用步数 + 与终点的曼哈顿距离(因为只是四方向行进)
现在感觉做这些模拟题,读题真的是关键中的关键,很容易踩坑,然后纠结很久;
难点关键:首先,炮台会帮你挡子弹;然后二维坐标上存在时间维度,这里需要用到三维数组记录状态,因为子弹的原因,因为最糟糕情况就是你这一时间被一个子弹挡住去路,
然后下一时间你又被挡住去路,并且当前位置会被子弹打到,需要往回走;所以单纯二维不够用;三维能用的又一原因在于题目存在体力值(相当于时间,用完就跳出),所以不会存在死循环;
这里子弹模拟计算,又是另一难点。看了其他人代码,有些是走到当前位置再去判断是否会被四周打到。
然后另一种更优方法就是用3维数组去记录子弹的动作,发挥想象力,就像是每一个时间维度下的二维就是幻灯片。然后你记录这些子弹轨迹,这样理解就简单多了。
#include <iostream> #include <cmath> #include <cstring> #include <queue> using namespace std; bool vis[102][102][1001],bullet[102][102][1001],ditu[102][102]; int m,n,k,d; int xx[] = {0,0,-1,1,0}; int yy[] = {-1,1,0,0,0}; struct castle{ int c,v,t,x,y; }ca[102]; struct node{ int x,y,step,f; bool operator<(const node &a)const{ return f>a.f; } node(int a,int b,int c) { x = a,y = b,step = c; f = step + abs(x - m) + abs(y - n); }; }; int checkD(char ch){ switch(ch){ case 'N':return 2; case 'S':return 3; case 'E':return 1; case 'W':return 0; } return -1; } bool check(int x,int y){ return x>=0&&x<=m&&y>=0&&y<=n; } void init(){ memset(bullet,false,sizeof bullet); for(int i=1;i<=k;i++){ int c = ca[i].c , v = ca[i].v,t = ca[i].t; for(int j=1;;j++){ int x = ca[i].x + xx[c]*j , y = ca[i].y + yy[c]*j; if(ditu[x][y]||!check(x,y)) break; if(j%v==0){ for(int z=0;z+j/v<=d;z+=t){ //cout<<i<<" "<<x<<" "<<y<<" "<<z+j/v<<endl; bullet[x][y][z+j/v] = true; } } } } } bool bfs(){ memset(vis,false,sizeof vis); priority_queue<node> q; q.push(node(0,0,0)); vis[0][0][0] = true; while(!q.empty()){ node p = q.top();q.pop(); //cout<<p.x<<" "<<p.y<<" "<<p.step<<endl; if(p.step>d) continue; if(p.x==m&&p.y==n){cout<<p.step<<endl;return true;} for(int i=0;i<5;i++){ int x = p.x + xx[i],y = p.y + yy[i] , step = p.step + 1; if(!check(x,y)||ditu[x][y]||vis[x][y][step]||bullet[x][y][step]) continue; vis[x][y][step] = true; q.push(node(x,y,step)); } } return false; } int main() { while(cin>>m>>n>>k>>d){ memset(ditu,false,sizeof ditu); char ch;int t,v,x,y; for(int i=1;i<=k;i++){ cin>>ch>>t>>v>>x>>y; ditu[x][y] = true; ca[i].c = checkD(ch); ca[i].t = t,ca[i].v = v , ca[i].x = x, ca[i].y = y; } //for(int i=1;i<=k;i++) cout<<ca[i].c<<" "<<ca[i].t<<" "<<ca[i].v<<" "<<ca[i].x<<" "<<ca[i].y<<endl; init(); /* for(int i=0;i<=d;i++){ for(int j=0;j<=m;j++){ for(int z=0;z<=n;z++) cout<<bullet[j][z][i]<<" "; cout<<endl; } cout<<endl; } */ if(!bfs()) puts("Bad luck!"); } return 0; }
E - DNA sequenceHDU - 1560
题目大意就是给出一堆字符串序列,然后求一个包含这些字符串序列的最小字符串长度。
算法就是 IDA* dfs
每一步进行字符串末尾添加,然后对那些需要包含的字符串用数组记录剩余长度。长度为0,则结束。最优估算剩余步数就是字符串中剩余最长长度。
limit开始使用被包含字符串最大长度,然后逐步最小增加。
#include <iostream> #include <cstring> #include <string.h> #include <algorithm> #include <cstdio> using namespace std; const int INF = 2e9; int n,nextd,limit; string s[9]; char ch[5] = "ACGT"; bool IDAstar(int step,int limit,int *len){ int h = 0; for(int i=0;i<n;i++) {int fk = s[i].length() - len[i];h = max(fk,h);} if(h==0) { cout<<step<<endl; return true;} if(h+step>limit) {nextd = min(nextd, h+step);return false;} for(int i=0;i<4;i++){ bool flag = false; int tnt[9]; for(int j=0;j<n;j++) if(ch[i] == s[j][len[j]]){ flag = true; tnt[j] = len[j] + 1; }else tnt[j] = len[j]; if(flag&&IDAstar(step+1,limit,tnt)) return true; } return false; } int main() { int _;cin>>_; while(_--){ cin>>n; limit = 0; int len[9] = {0}; for(int i=0;i<n;i++) { cin>>s[i]; int fk = s[i].length(); limit = max(limit,fk); } //for(int i=0;i<n;i++) cout<<s[i]<<endl; while(1){ nextd = INF; if(IDAstar(0,limit,len)) break; limit = nextd; } } return 0; }
拓展思考:这题如果直接给出一个目标字符串,然后允许你插入,移动字符串直到全包含。感觉题目就难一点了。
难点在于增加了任意位置插入字符操作,而不是简单的末尾添加,并且你可以移动,相当于位置替换。
现在我还没想到怎么最优解.....等哪一天想到;或者做得类似题目就补上。
F - Magic CubeZOJ - 2477
最痛苦的一道题,大意都理解,问题出在细节。
算法:IDA* 优化dfs,,普通dfs也能过
limit给出了需要5步内,就直接从0开始慢慢加到5;
IDA启发函数就相当于最优情况下的估算步数+当前步数,这里你想,一个操作可最多改变3*4的单位(不考虑顺序)。所以你记录6个面不同与中心方格的数量,然后除以12,向上取整就是最优估算。
剪枝就是不走前面一步同一面的反方向,比如(1,-1)与(1,1)会相互抵消效果
开始了,最痛苦的细节,本题我出错的地方,方便那些不知道错误地方的同志留意。
1.就是你转的那个面,它自身图案顺序会转动,模拟真实魔方就知道。
2.题目给出颜色 rg什么的,不是判断标准,你要以输入的面中心方格颜色为标准。
3.向上取整的ceil函数里面的数一定要是浮点型。。。。我哭了 wzc
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <cmath> using namespace std; char mp[6][4][4]; int ans[6][3]; int check(){ double ans = 0; for(int i=0;i<6;i++) for(int x=0;x<3;x++) for(int y=0;y<3;y++) if(mp[i][x][y]!=mp[i][1][1]) ans++; return ceil(ans/12); } //================================================== void op2(int x,int y){ if(y==-1){ char ch = mp[x][0][0];mp[x][0][0] = mp[x][0][2];mp[x][0][2] = mp[x][2][2]; mp[x][2][2] = mp[x][2][0];mp[x][2][0] = ch; ch = mp[x][0][1];mp[x][0][1] = mp[x][1][2];mp[x][1][2] = mp[x][2][1]; mp[x][2][1] = mp[x][1][0];mp[x][1][0] = ch; }else{ char ch = mp[x][0][0];mp[x][0][0] = mp[x][2][0];mp[x][2][0] = mp[x][2][2]; mp[x][2][2] = mp[x][0][2];mp[x][0][2] = ch; ch = mp[x][0][1];mp[x][0][1] = mp[x][1][0];mp[x][1][0] = mp[x][2][1]; mp[x][2][1] = mp[x][1][2];mp[x][1][2] = ch; } } void op(int x,int y){ op2(x,y); switch(x){ case 0: if(y==-1){ char a = mp[4][0][0],b = mp[4][1][0],c = mp[4][2][0]; mp[4][0][0] = mp[1][0][0];mp[4][1][0] = mp[1][1][0];mp[4][2][0] = mp[1][2][0]; mp[1][0][0] = mp[5][0][0];mp[1][1][0] = mp[5][1][0];mp[1][2][0] = mp[5][2][0]; mp[5][0][0] = mp[3][2][2];mp[5][1][0] = mp[3][1][2];mp[5][2][0] = mp[3][0][2]; mp[3][0][2] = c;mp[3][1][2] = b;mp[3][2][2] = a; }else{ char a = mp[4][0][0],b = mp[4][1][0],c = mp[4][2][0]; mp[4][0][0] = mp[3][2][2];mp[4][1][0] = mp[3][1][2];mp[4][2][0] = mp[3][0][2]; mp[3][2][2] = mp[5][0][0];mp[3][1][2] = mp[5][1][0];mp[3][0][2] = mp[5][2][0]; mp[5][0][0] = mp[1][0][0];mp[5][1][0] = mp[1][1][0];mp[5][2][0] = mp[1][2][0]; mp[1][0][0] = a;mp[1][1][0] = b;mp[1][2][0] = c; }break; case 1: if(y==-1){ char a = mp[4][2][0],b = mp[4][2][1],c = mp[4][2][2]; mp[4][2][0] = mp[2][0][0];mp[4][2][1] = mp[2][1][0];mp[4][2][2] = mp[2][2][0]; mp[2][0][0] = mp[5][0][2];mp[2][1][0] = mp[5][0][1];mp[2][2][0] = mp[5][0][0]; mp[5][0][2] = mp[0][2][2];mp[5][0][1] = mp[0][1][2];mp[5][0][0] = mp[0][0][2]; mp[0][2][2] = a;mp[0][1][2] = b;mp[0][0][2] = c; }else{ char a = mp[4][2][0],b = mp[4][2][1],c = mp[4][2][2]; mp[4][2][0] = mp[0][2][2];mp[4][2][1] = mp[0][1][2];mp[4][2][2] = mp[0][0][2]; mp[0][2][2] = mp[5][0][2];mp[0][1][2] = mp[5][0][1];mp[0][0][2] = mp[5][0][0]; mp[5][0][2] = mp[2][0][0];mp[5][0][1] = mp[2][1][0];mp[5][0][0] = mp[2][2][0]; mp[2][0][0] = a;mp[2][1][0] = b;mp[2][2][0] = c; }break; case 2: if(y==-1){ char a = mp[4][0][2],b = mp[4][1][2],c = mp[4][2][2]; mp[4][0][2] = mp[3][2][0];mp[4][1][2] = mp[3][1][0];mp[4][2][2] = mp[3][0][0]; mp[3][2][0] = mp[5][0][2];mp[3][1][0] = mp[5][1][2];mp[3][0][0] = mp[5][2][2]; mp[5][0][2] = mp[1][0][2];mp[5][1][2] = mp[1][1][2];mp[5][2][2] = mp[1][2][2]; mp[1][0][2] = a;mp[1][1][2] = b;mp[1][2][2] = c; }else{ char a = mp[4][0][2],b = mp[4][1][2],c = mp[4][2][2]; mp[4][0][2] = mp[1][0][2];mp[4][1][2] = mp[1][1][2];mp[4][2][2] = mp[1][2][2]; mp[1][0][2] = mp[5][0][2];mp[1][1][2] = mp[5][1][2];mp[1][2][2] = mp[5][2][2]; mp[5][0][2] = mp[3][2][0];mp[5][1][2] = mp[3][1][0];mp[5][2][2] = mp[3][0][0]; mp[3][2][0] = a;mp[3][1][0] = b;mp[3][0][0] = c; }break; case 3: if(y==-1){ char a = mp[4][0][0],b = mp[4][0][1],c = mp[4][0][2]; mp[4][0][0] = mp[0][2][0];mp[4][0][1] = mp[0][1][0];mp[4][0][2] = mp[0][0][0]; mp[0][2][0] = mp[5][2][2];mp[0][1][0] = mp[5][2][1];mp[0][0][0] = mp[5][2][0]; mp[5][2][2] = mp[2][0][2];mp[5][2][1] = mp[2][1][2];mp[5][2][0] = mp[2][2][2]; mp[2][0][2] = a;mp[2][1][2] = b;mp[2][2][2] = c; }else{ char a = mp[4][0][0],b = mp[4][0][1],c = mp[4][0][2]; mp[4][0][0] = mp[2][0][2];mp[4][0][1] = mp[2][1][2];mp[4][0][2] = mp[2][2][2]; mp[2][0][2] = mp[5][2][2];mp[2][1][2] = mp[5][2][1];mp[2][2][2] = mp[5][2][0]; mp[5][2][2] = mp[0][2][0];mp[5][2][1] = mp[0][1][0];mp[5][2][0] = mp[0][0][0]; mp[0][2][0] = a;mp[0][1][0] = b;mp[0][0][0] = c; }break; case 4: if(y==-1){ char a = mp[3][0][0],b = mp[3][0][1],c = mp[3][0][2]; mp[3][0][0] = mp[2][0][0];mp[3][0][1] = mp[2][0][1];mp[3][0][2] = mp[2][0][2]; mp[2][0][0] = mp[1][0][0];mp[2][0][1] = mp[1][0][1];mp[2][0][2] = mp[1][0][2]; mp[1][0][0] = mp[0][0][0];mp[1][0][1] = mp[0][0][1];mp[1][0][2] = mp[0][0][2]; mp[0][0][0] = a;mp[0][0][1] = b;mp[0][0][2] = c; }else{ char a = mp[3][0][0],b = mp[3][0][1],c = mp[3][0][2]; mp[3][0][0] = mp[0][0][0];mp[3][0][1] = mp[0][0][1];mp[3][0][2] = mp[0][0][2]; mp[0][0][0] = mp[1][0][0];mp[0][0][1] = mp[1][0][1];mp[0][0][2] = mp[1][0][2]; mp[1][0][0] = mp[2][0][0];mp[1][0][1] = mp[2][0][1];mp[1][0][2] = mp[2][0][2]; mp[2][0][0] = a;mp[2][0][1] = b;mp[2][0][2] = c; }break; case 5: if(y==-1){ char a = mp[1][2][0],b = mp[1][2][1],c = mp[1][2][2]; mp[1][2][0] = mp[2][2][0];mp[1][2][1] = mp[2][2][1];mp[1][2][2] = mp[2][2][2]; mp[2][2][0] = mp[3][2][0];mp[2][2][1] = mp[3][2][1];mp[2][2][2] = mp[3][2][2]; mp[3][2][0] = mp[0][2][0];mp[3][2][1] = mp[0][2][1];mp[3][2][2] = mp[0][2][2]; mp[0][2][0] = a;mp[0][2][1] = b;mp[0][2][2] = c; }else{ char a = mp[1][2][0],b = mp[1][2][1],c = mp[1][2][2]; mp[1][2][0] = mp[0][2][0];mp[1][2][1] = mp[0][2][1];mp[1][2][2] = mp[0][2][2]; mp[0][2][0] = mp[3][2][0];mp[0][2][1] = mp[3][2][1];mp[0][2][2] = mp[3][2][2]; mp[3][2][0] = mp[2][2][0];mp[3][2][1] = mp[2][2][1];mp[3][2][2] = mp[2][2][2]; mp[2][2][0] = a;mp[2][2][1] = b;mp[2][2][2] = c; }break; } } //================================================ int dfs(int step,int prex,int prey,int limit){ int h = check(); if(step+h>limit) return 0; if(h==0) return 1; for(int i=0;i<6;i++) for(int j=0,z=-1;j<2;j++,z+=2){ if(prex == i && prey+z == 0) continue; op(i,z); if(dfs(step+1,i,z,limit)) {ans[step][0]=i,ans[step][1]=z;return 1;} op(i,-z); } return 0; } //================================================ int main() { int _;scanf("%d",&_); while(_--){ char ch; for(int i=0;i<3;i++) for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[4][i][j] = ch; } for(int i=0;i<3;i++){ for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[0][i][j] = ch; } for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[1][i][j] = ch; } for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[2][i][j] = ch; } for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[3][i][j] = ch; } } for(int i=0;i<3;i++){ for(int j=0;j<3;j++){ ch = getchar();while(ch=='\n'||ch==' ') ch=getchar(); mp[5][i][j] = ch; } } int limit; for(limit = 0;limit<6;limit++) if(dfs(0,-1,0,limit)==1){ printf("%d\n",limit); for(int i=0;i<limit;i++) printf("%d %d\n",ans[i][0],ans[i][1]); break; } if(limit == 6) puts("-1"); } return 0; }
算法:BFS+曼哈顿距离
难点:一开始 突发奇想,一秒走多步?这可以尝试BFS+DFS吗?然后后面wrong了,改成正常思维一个点进一个新队列一下BFS3次,然后还wrong;
然后想想因为标记函数(用于避免重复入队),一个点一下走3步,第3步标记的点可能是其他情况的第2步,导致其无法入队并且走出其下一步,导致错误答案;
所以需要每个同阶段的点进行一步步判断。
第二点优化,我是看其他人代码学到的,就是evil不需要入队进行随着时间扩散,只要记住两个起点,然后用曼哈顿距离(两个点之间的step数,因为鬼魂无视障碍,所以可以直接使用)。
注意点:就是鬼魂无视障碍并且最先走。每一个点判断前需要判断,他位置这个时刻是否有鬼在。
第三点:我的出错点,标记函数并不是用于判断答案的前提条件,而是作为一个避免重复入队操作函数。
#include <iostream> #include <cstring> #include <queue> #include <cmath> #include <vector> using namespace std; bool ditu[888][888],boy[888][888],girl[888][888]; int m,n,step; int Zx[2],Zy[2],num; int xx[] = {1,0,0,-1}; int yy[] = {0,1,-1,0}; struct node{ int x,y; node(int a,int b):x(a),y(b) {}; }; queue<node> q1,q2; void init(){ memset(ditu,true,sizeof ditu); memset(boy,true,sizeof boy); memset(girl,true,sizeof girl); while(!q1.empty()) q1.pop(); while(!q2.empty()) q2.pop(); num = 0;step=0; cin>>n>>m; for(int i=0;i<n;i++) for(int j=0;j<m;j++){ char ch;ch = getchar();while(ch=='\n'||ch==' ') ch = getchar(); if(ch=='X') ditu[i][j] = false; else if(ch=='Z') Zx[num] = i,Zy[num++] = j; else if(ch=='M') q1.push(node(i,j)),boy[i][j] = false; else if(ch=='G') q2.push(node(i,j)),girl[i][j] = false; } } bool check(int x,int y){ return x>=0&&x<n&&y>=0&&y<m&&ditu[x][y]&&(abs(x-Zx[0])+abs(y-Zy[0])>step*2)&&(abs(x-Zx[1])+abs(y-Zy[1])>step*2); } bool bfs(){ while(!q1.empty()&&!q2.empty()){ step++; int s = q2.size(); while(s--){ node p = q2.front();q2.pop(); if(!check(p.x,p.y)) continue; for(int i=0;i<4;i++){ int x = p.x+xx[i],y = p.y+yy[i]; if(!check(x,y)) continue; if(!boy[x][y]) {cout<<step<<endl;return true;} if(girl[x][y]){ girl[x][y] = false;q2.push(node(x,y)); } } } for(int i=0;i<3;i++){ int s = q1.size(); while(s--){ node p = q1.front();q1.pop(); if(!check(p.x,p.y)) continue; for(int i=0;i<4;i++){ int x = p.x+xx[i],y = p.y+yy[i]; if(!check(x,y)) continue; if(!girl[x][y]) {cout<<step<<endl;return true;} if(boy[x][y]){ boy[x][y] = false;q1.push(node(x,y)); } } } } } return false; } int main() { int _;cin>>_; while(_--){ init(); if(!bfs()) puts("-1"); } return 0; }
算法:BFS+hash/Map
题意:就是给出矩阵数组,然后11,21,31,41提到最左边,留出空,然后填空规则为,数一定是空左边数+1,空左边为空或者数为*7则无法填这个空
难点在于判重,这里我用了两种办法。
一种就是Map,因为11,21,31,41再移位之后都为空,所以有效数字为4*6+1为25,刚好小于英文字母,不过要写个对应的map匹配数组用于映射。
后面我看了别人代码,发现AS2码中,题目中的数字都各自代表不同的字符,可以直接使用。
这里我用char数组存储,然后在最末尾加个‘\0’ 即可直接当作string使用。
#include <iostream> #include <queue> #include <map> #include <cstring> using namespace std; char fin[33] = { 11,12,13,14,15,16,17,1, 21,22,23,24,25,26,27,1, 31,32,33,34,35,36,37,1, 41,42,43,44,45,46,47,1,'\0'}; char sta[33]; struct node{ char str[33]; int step; node(char ch[33],int s){ strcpy(str,ch); step = s; } bool check(){ for(int i=0;i<32;i++) if(str[i]!=fin[i]) return false; return true; } int Move(int val){ for(int i=0;i<32;i++) if(str[i]==val) return i; } }; bool bfs(){ queue<node> q; map<string,bool> mp; q.push(node(sta,0)); mp[sta] = true; while(!q.empty()){ node p = q.front();q.pop(); if(p.check()) {cout<<p.step<<endl;return true;} for(int i=0;i<32;i++) if(p.str[i]==1&&p.str[i-1]!=1&&p.str[i-1]%10!=7){ int index = p.Move(p.str[i-1]+1); swap(p.str[i],p.str[index]); if(!mp[p.str]){ mp[p.str] = true; q.push(node(p.str,p.step+1)); } swap(p.str[i],p.str[index]); } } return false; } int main() { int _;cin>>_; while(_--){ for(int i = 1;i<33;){ for(int j=0;j<7;j++){ int ch;cin>>ch; if(ch==11||ch==21||ch==31||ch==41) sta[i++] = 1; else sta[i++] = ch; }i++; }sta[0] = 11,sta[8] = 21,sta[16] = 31,sta[24] = 41;sta[32]= '\0'; if(!bfs()) puts("-1"); } return 0; }
第二种就是hash;
hash分两部分,一个是hashValue公式,和hash冲突处理。
我这里选取2进制权值求和,冲突处理MOD1000007,然后+10MOD1000007.
#include <iostream> #include <queue> #include <map> #include <cstring> using namespace std; int fin[32] = { 11,12,13,14,15,16,17,1, 21,22,23,24,25,26,27,1, 31,32,33,34,35,36,37,1, 41,42,43,44,45,46,47,1}; int sta[32], stb[4]; struct node{ int str[32]; int step; int hole[4]; node(int ch[32],int s,int a[4]){ for(int i=0;i<32;i++) str[i] = ch[i]; step = s; for(int i=0;i<4;i++) hole[i] = a[i]; } bool check(){ for(int i=0;i<32;i++) if(str[i]!=fin[i]) return false; return true; } int Move(int val){ for(int i=0;i<32;i++) if(str[i]==val) return i; } }; const int MOD = 1000007; long long hashValue[MOD*2]; long long hashCode(int ch[32]){ long long sum = 0,tnt = 1; for(int i=0;i<32;i++) sum += ch[i]*tnt,tnt*=2; return sum; } bool insertHash(int ch[32]){ long long v = hashCode(ch); long long key = v%MOD; while(hashValue[key] != -1&&hashValue[key] != v) key = key+10%MOD; if(hashValue[key] == -1) {hashValue[key]=v;return true;} return false; } bool bfs(){ memset(hashValue,-1,sizeof hashValue); queue<node> q; q.push(node(sta,0,stb)); insertHash(sta); while(!q.empty()){ node p = q.front();q.pop(); if(p.check()) {cout<<p.step<<endl;return true;} for(int i=0;i<4;i++){ int ph = p.hole[i]; if(p.str[ph-1]!=1&&p.str[ph-1]%10!=7){ int index = p.Move(p.str[ph-1]+1); node pp = p; swap(pp.str[ph],pp.str[index]); pp.hole[i] = index; pp.step = p.step + 1; if(insertHash(pp.str)) q.push(pp); } } } return false; } int main() { int _;cin>>_; while(_--){ for(int i = 1,h = 0;i<33;){ for(int j=0;j<7;j++){ int ch;cin>>ch; if(ch==11||ch==21||ch==31||ch==41) stb[h++] = i,sta[i++] = 1; else sta[i++] = ch; }i++; }sta[0] = 11,sta[8] = 21,sta[16] = 31,sta[24] = 41; //cout<<endl; //for(int i=0;i<32;i++){ cout<<sta[i]<<" ";if(i==7||i==15||i==23||i==31) cout<<endl;} //for(int i=0;i<4;i++) cout<<stb[i]<<" ";cout<<endl; if(!bfs()) puts("-1"); } return 0; }
A计划 HDU - 2102
算法就是 bfs
难点:在于题意中的判断,因为就两层还好,传送门是否可以通过?
#include <iostream> #include <queue> #include <cstring> using namespace std; const int INF = 2e9; int n,m,t; int Px,Py,Pz; int ditu[11][11][3]; bool vis[11][11][3]; int xx[] = {1,0,-1,0}; int yy[] = {0,1,0,-1}; struct node{ int x,y,z,step; node(int a,int b,int c,int d):x(a),y(b),z(c),step(d) {}; }; queue<node> q; void init(){ memset(ditu,0,sizeof ditu); memset(vis,true,sizeof vis); while(!q.empty()) q.pop(); cin>>n>>m>>t; for(int z=0;z<2;z++) for(int i=0;i<n;i++) for(int j=0;j<m;j++){ char ch = getchar();while(ch==' '||ch=='\n') ch=getchar(); if(ch=='*') ditu[i][j][z] = -1; else if(ch=='#') ditu[i][j][z] = 1; else if(ch=='S') q.push(node(i,j,z,0)),vis[i][j][z] = false; else if(ch=='P') Px = i,Py = j,Pz = z; } } bool check(int x,int y,int z){ if(x<0||x>=n||y<0||y>=m||z<0||z>=2) return false; if(ditu[x][y][z]==-1) return false; if(ditu[x][y][z]==1&&(ditu[x][y][z^1]==-1||ditu[x][y][z^1]==1)) return false; return true; } bool bfs(){ while(!q.empty()){ node p = q.front();q.pop(); if(p.step>t) continue; if(Px==p.x&&Py==p.y&&Pz==p.z) return true; for(int i=0;i<4;i++){ int x = p.x + xx[i] , y = p.y + yy[i],z = p.z; if(!check(x,y,z)||!vis[x][y][z]) continue; vis[x][y][z] = false;if(ditu[x][y][z]==1) z^=1;vis[x][y][z] = false; q.push(node(x,y,z,p.step+1)); } } return false; } int main() { int _;cin>>_; while(_--){ init(); if(bfs()) puts("YES"); else puts("NO"); } return 0; }
J - TravellingHDU - 3001
算法:3进制状态压缩+dp/bfs
难点在于:题意理解。
But Mr Acmer gets bored so easily that he doesn't want to visit a city more than twice!
由于这里我看太快了,意思为不能。。2次,误解为只能经过一次,然后就感觉简单,dfs+剪枝然后一直wrong。
后面看了看,原来是不能超过2次。所以状态为0,1,2.即经过的次数。用3进制进行状态压缩。
愿意就是acmer可以为了达到经过所有地点并且路费最低,允许他经过一个点不超过2次。
第二难点在于3进制预处理。枚举所有可能的状态,然后用数组存储各种状态下点的经过数。
第三就是状态最优更新。
dp[i][j]: i为3进制状态,j为最后的连接点。
1.如果状态i,检测出所有点是否经过,然后进行答案更新
2.通过遍历点j的边进行向下寻找符合进入的点,进行dp数组更新
答案:
1.bfs(更加易懂)
#include <iostream> #include <vector> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int INF = 0x3f3f3f3f; int n,m,ans; int path[11][11]; int state[65432][11],three[11]; //three[i]表示3的i次方,state[i][j]表示3进制状态中j位的情况(情况分别为0,1,2,代表一个点经过数) int dp[65432][11]; //dp[i][j]3进制状态下情况以j点作为最后连接点的最优即最少代价 //(状态下一步:找经过数为0或1的点,然后进行向下添加状态,最优则更新;) void init(){// three[0] = 1; for(int i=1;i<11;i++) three[i] = three[i-1]*3; for(int i=0;i<three[10];i++){ int tmp = i; for(int j=0;j<10;j++) state[i][j] = tmp%3,tmp/=3; } } struct node{ int stat,now,free; node (int a,int b,int c):stat(a),now(b),free(c) {} bool check(){ for(int i=0;i<n;i++) if(state[stat][i]==0) return false; return true; } }; void bfs(int x){ queue<node> q; dp[three[x]][x] = 0; q.push(node(three[x],x,0)); while(!q.empty()){ node p = q.front();q.pop(); if(p.check()) {ans = min(ans,p.free);continue;} if(p.free >= ans) continue;//剪枝 for(int i=0;i<n;i++){ if(p.now==i || state[p.stat][i]>1 || path[p.now][i]==INF) continue; int cost = p.free + path[p.now][i]; if(cost >= ans) continue;//剪枝 if(dp[p.stat+three[i]][i]!=INF&&dp[p.stat+three[i]][i]<=cost) continue; //这里dp函数作用在于判重并且是否最优,没可能最优肯定不能入队 dp[p.stat+three[i]][i] = cost; q.push(node(p.stat+three[i],i,cost)); } } } void solve(){ memset(dp,INF,sizeof dp); for(int i=0;i<n;i++) bfs(i); } int main() { init(); while(cin>>n>>m){ memset(path,INF,sizeof path); for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;path[a-1][b-1] = min(path[a-1][b-1],c);path[b-1][a-1] = path[a-1][b-1];} ans = INF; solve(); if(ans==INF) puts("-1"); else cout<<ans<<endl; } return 0; }
2.状态dp(其实与bfs无异,通过3循环遍历,一循环状态i,二循环尾点j,三循环进行下一连接点k查找)
#include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int n,m,ans; int path[11][11]; int state[65432][11],three[11]; //three[i]表示3的i次方,state[i][j]表示3进制状态中j位的情况(情况分别为0,1,2,代表一个点经过数) int dp[65432][11]; //dp[i][j]3进制状态下情况以j点作为最后连接点的最优即最少代价 //(状态下一步:找连接数为0或1的点,然后进行向下添加状态,最优则更新;) void init(){// three[0] = 1; for(int i=1;i<11;i++) three[i] = three[i-1]*3; for(int i=0;i<three[10];i++){ int tmp = i; for(int j=0;j<10;j++) state[i][j] = tmp%3,tmp/=3; } } void solve(){ memset(dp,INF,sizeof dp); //点自己到自己代价为0 for(int i=0;i<n;i++) dp[three[i]][i] = 0; //从状态0开始向后更新,后面状态无法影响前面状态, //所以对于全联通的状态进行0~n-1结尾进行最终答案更新就行了 for(int i=0;i<three[n];i++){ bool flag = true; for(int j=0;j<n;j++){ if(state[i][j]==0) {flag = false;continue;} //if(state[i][j]==2) continue; for(int k=0;k<n;k++){ if(path[j][k]==INF||k==j||state[i][k]==2) continue; if(dp[i+three[k]][k] <= dp[i][j]+path[j][k]) continue; dp[i+three[k]][k] = dp[i][j]+path[j][k]; } } if(flag) {for(int j=0;j<n;j++) ans = min(ans,dp[i][j]);} } } int main() { init(); while(cin>>n>>m){ memset(path,INF,sizeof path); for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;path[a-1][b-1] = min(path[a-1][b-1],c);path[b-1][a-1] = path[a-1][b-1];} ans = INF; solve(); if(ans==INF) puts("-1"); else cout<<ans<<endl; } return 0; }