难度:提高-
题目类型:BFS
提交次数:3
涉及知识:BFS
描述
多灾多难的公主又被大魔王抓走啦!国王派遣了第一勇士阿福去拯救她。
身为超级厉害的术士,同时也是阿福的好伙伴,你决定祝他一臂之力。你为阿福提供了一张大魔王根据地的地图,上面标记了阿福和公主所在的位置,以及一些不能够踏入的禁区。你还贴心地为阿福制造了一些传送门,通过一个传送门可以瞬间转移到任意一个传送门,当然阿福也可以选择不通过传送门瞬移。传送门的位置也被标记在了地图上。此外,你还查探到公主所在的地方被设下了结界,需要集齐K种宝石才能打开。当然,你在地图上也标记出了不同宝石所在的位置。
你希望阿福能够带着公主早日凯旋。于是在阿福出发之前,你还需要为阿福计算出他最快救出公主的时间。
地图用一个R×C的字符矩阵来表示。字符S表示阿福所在的位置,字符E表示公主所在的位置,字符#表示不能踏入的禁区,字符$表示传送门,字符.表示该位置安全,数字字符0至4表示了宝石的类型。阿福每次可以从当前的位置走到他上下左右四个方向上的任意一个位置,但不能走出地图边界。阿福每走一步需要花费1个单位时间,从一个传送门到达另一个传送门不需要花费时间。当阿福走到宝石所在的位置时,就视为得到了该宝石,不需要花费额外时间。
输入第一行是一个正整数T(1 <= T <= 10),表示一共有T组数据。
每一组数据的第一行包含了三个用空格分开的正整数R、C(2 <= R, C <= 200)和K,表示地图是一个R×C的矩阵,而阿福需要集齐K种宝石才能够打开拘禁公主的结界。
接下来的R行描述了地图的具体内容,每一行包含了C个字符。字符含义如题目描述中所述。保证有且仅有一个S和E。$的数量不超过10个。宝石的类型在数字0至4范围内,即不会超过5种宝石。输出对于每一组数据,输出阿福救出公主所花费的最少单位时间。若阿福无法救出公主,则输出“oop!”(只输出引号里面的内容,不输出引号)。每组数据的输出结果占一行。
样例输入
1 7 8 2 ........ ..S..#0. .##..1.. .0#..... ...1#... ...##E.. ...1....
样例输出
11 代码:
1 #include<iostream> 2 #include<cstring> 3 #include<queue> 4 #include<vector> 5 #include<cmath> 6 using namespace std; 7 int t; 8 char map[205][205]; 9 bool diamond[5]; 10 struct node{ 11 int x, y; 12 int jewel,time; 13 node(int xx, int yy, int j, int t):x(xx),y(yy),jewel(j),time(t){} 14 }; 15 bool operator<(const node&a, const node&b){ 16 return a.time>b.time; 17 } 18 priority_queue<node> pq; 19 bool visited[205][205][20]; 20 int dirx[4] = {0,0,1,-1}; 21 int diry[4] = {-1,1,0,0}; 22 vector<node>v; 23 24 bool judge(int x, int y){ 25 if(map[x][y]!=‘#‘&&map[x][y]!=0) 26 return true; 27 return false; 28 } 29 30 bool isjewel(int x, int y){ 31 if(map[x][y]==‘0‘||map[x][y]==‘1‘||map[x][y]==‘2‘||map[x][y]==‘3‘||map[x][y]==‘4‘) 32 return true; 33 return false; 34 } 35 36 bool finished(int n){ 37 int temp = 0; 38 for(int i = 1; i <= 32; i*=2){ 39 temp++; 40 if((i&n)==0&&diamond[temp-1]!=0) 41 return false; 42 } 43 return true; 44 } 45 int main(){ 46 cin>>t; 47 while(t--){ 48 int r, c, k; 49 cin>>r>>c>>k; 50 memset(map, 0, sizeof(map)); 51 memset(visited,0,sizeof(visited)); 52 memset(diamond,0,sizeof(diamond)); 53 while(!pq.empty())pq.pop(); 54 v.clear(); 55 int i, j; 56 for(i = 1; i <= r; i++) 57 for(j = 1; j <= c; j++){ 58 cin>>map[i][j]; 59 if(map[i][j]==‘S‘){ 60 pq.push(node(i,j,0,0)); 61 visited[i][j][0] = true; 62 } 63 else if(map[i][j]==‘$‘) 64 v.push_back(node(i,j,0,0)); 65 else if(map[i][j]>=‘0‘&&map[i][j]<=‘4‘){ 66 diamond[map[i][j]-‘0‘] = true; 67 } 68 } 69 int flag = 0; 70 while(!pq.empty()){ 71 node no = pq.top(); 72 pq.pop(); 73 // cout<<no.x<<" "<<no.y<<" "<<no.jewel<<" "<<no.time<<":"<<endl; 74 if(map[no.x][no.y]==‘E‘&&finished(no.jewel)){ 75 cout<<no.time<<endl; 76 flag = 1; 77 break; 78 } 79 for(i = 0; i < 4; i++){ 80 int nx = no.x+dirx[i]; 81 int ny = no.y+diry[i]; 82 int nj = no.jewel; 83 if(isjewel(nx,ny)){ 84 if((int(pow(2,map[nx][ny]-‘0‘))&no.jewel)==0){ 85 nj+=pow(2, map[nx][ny]-‘0‘); 86 } 87 }//位运算 加钻石 88 if(!visited[nx][ny][nj]&&judge(nx,ny)){ 89 if(map[nx][ny]==‘$‘){ 90 for(j = 0; j < v.size(); j++){ 91 pq.push(node(v[j].x,v[j].y,nj,no.time+1)); 92 visited[v[j].x][v[j].y][nj] = true; 93 // cout<<v[j].x<<" "<<v[j].y<<" "<<nj<<" "<<no.time+1<<endl; 94 95 } 96 } 97 else{ 98 pq.push(node(nx,ny,nj,no.time+1)); 99 visited[nx][ny][nj] = true; 100 // cout<<nx<<" "<<ny<<" "<<nj<<" "<<no.time+1<<endl; 101 } 102 } 103 } 104 // getchar(); 105 } 106 if(!flag) cout<<"oop!"<<endl; 107 } 108 return 0; 109 }
备注:
昨天调到一点。。然后就睡觉了。今天起来给调过了。
掌握了调试BFS的好方法。。如代码中注释所示。
这种带各种附加条件的BFS就是在裸的BFS外面包了一层纸,只要把结点的状态设置丰富一点就行了。用老师的话说图是“立体”的,虽然我觉得这个比喻反而不容易理解。
对于传送门的处理方式是,如果下一步走到的是传送门,就同时把所有的传送门(输入时存在了vector里)加到队列里。
最近老犯的错误就是一个语句块不加大括号。。
加钻石那块也弄错来着。我用的是一个数存(因为是5种钻石所以不超过32-1)。老师给数据之前不知道钻石序号不挨着。
判断finished用了2的5次方的复杂度,其实可以在外面算出来finished的jewel大小,这样就把判断的复杂度降成O(1)了。一会儿我再改一改。
很重要的一个问题,就是因为涉及到位运算,优先级搞错了好几次!
优先级 | 操作符 | 描述 | 例子 | 结合性 |
---|---|---|---|---|
1 | () [] -> . :: ++ -- |
调节优先级的括号操作符 数组下标访问操作符 通过指向对象的指针访问成员的操作符 通过对象本身访问成员的操作符 作用域操作符 后置自增操作符 后置自减操作符 |
(a + b) / 4; array[4] = 2; ptr->age = 34; obj.age = 34; Class::age = 2; for( i = 0; i < 10; i++ ) ... for( i = 10; i > 0; i-- ) ... |
从左到右 |
2 | ! ~ ++ -- - + * & (type) sizeof |
逻辑取反操作符 按位取反(按位取补) 前置自增操作符 前置自减操作符 一元取负操作符 一元取正操作符 解引用操作符 取地址操作符 类型转换操作符 返回对象占用的字节数操作符 |
if( !done ) ... flags = ~flags; for( i = 0; i < 10; ++i ) ... for( i = 10; i > 0; --i ) ... int i = -1; int i = +1; data = *ptr; address = &obj; int i = (int) floatNum; int size = sizeof(floatNum); |
从右到左 |
3 | ->* .* |
在指针上通过指向成员的指针访问成员的操作符 在对象上通过指向成员的指针访问成员的操作符 |
ptr->*var = 24; obj.*var = 24; |
从左到右 |
4 | * / % |
乘法操作符 除法操作符 取余数操作符 |
int i = 2 * 4; float f = 10 / 3; int rem = 4 % 3; |
从左到右 |
5 | + - |
加法操作符 减法操作符 |
int i = 2 + 3; int i = 5 - 1; |
从左到右 |
6 | << >> |
按位左移操作符 按位右移操作符 |
int flags = 33 << 1; int flags = 33 >> 1; |
从左到右 |
7 | < <= > >= |
小于比较操作符 小于或等于比较操作符 大于比较操作符 大于或等于比较操作符 |
if( i < 42 ) ... if( i <= 42 ) ... if( i > 42 ) ... if( i >= 42 ) ... |
从左到右 |
8 | == != |
等于比较操作符 不等于比较操作符 |
if( i == 42 ) ... if( i != 42 ) ... |
从左到右 |
9 | & | 按位与操作符 | flags = flags & 42; | 从左到右 |
10 | ^ | 按位异或操作符 | flags = flags ^ 42; | 从左到右 |
11 | | | 按位或操作符 | flags = flags | 42; | 从左到右 |
12 | && | 逻辑与操作符 | if( conditionA && conditionB ) ... | 从左到右 |
13 | || | 逻辑或操作符 | if( conditionA || conditionB ) ... | 从左到右 |
14 | ? : | 三元条件操作符 | int i = (a > b) ? a : b; | 从右到左 |
15 | = += -= *= /= %= &= ^= |= <<= >>= |
赋值操作符 复合赋值操作符(加法) 复合赋值操作符(减法) 复合赋值操作符(乘法) 复合赋值操作符(除法) 复合赋值操作符(取余) 复合赋值操作符(按位与) 复合赋值操作符(按位异或) 复合赋值操作符(按位或) 复合赋值操作符(按位左移) 复合赋值操作符(按位右移) |
int a = b; a += 3; b -= 4; a *= 5; a /= 2; a %= 3; flags &= new_flags; flags ^= new_flags; flags |= new_flags; flags <<= 2; flags >>= 2; |
从右到左 |
16 | , | 逗号操作符 | for( i = 0, j = 0; i < 10; i++, j++ ) ... | 从左到右 |
从http://www.cnblogs.com/ywl925/p/3710246.html抄过来的。注意按为与(&)比==低!!!其他的优先级也要注意一下。