uva1601(The morning after Helloween)解题报告

最先考虑到的当然是BFS,想当然地认为只要一个一个地BFS就可以了。就像是迷宫类的题目,一步一步找,最终找到了某一个状态就可以了,只不过人数多了点的走迷宫而已。但是如果这么做的话必然会超时,因为在每一个状态可以走的路太多,三个鬼,每个有5个方向可以走,三个加起来除去原地不动还有124种走法,而且算出来的最少步数也不一定少,第三组样例的步数就多达77步,空间有没有那么多就暂且不说了,时间会超,在uva上亲测过。所以必须要优化了。

首先就是受到了刘汝佳的提示了,他在书中说提取空格,因为‘#’占大多数,把空格提取出来就可以减少很多无用的搜索。

如果每次都判断小写字母的下一步是否合法,那就是说每次移动都需要判断5^3,超时了。

把所有可以移动的格子找出来建立一张图,就是把障碍物给删除,统计每个可以空格或者有鬼的格子可以移动到哪些格子,这样在判断的时候就节省了许多时间。然后BFS找最短路。

所以顺着这个思路就写了。当然也受到了刘汝佳的代码的熏陶了。
#include
#include
#include
#include
using namespace std;
const int MAXN=16;
const int MAXM=192;
int getnum(int a, int b, int c)
{  

	return (a<<16) | (b<<8) | c;
//a,b,C不超256(16*16)不过是8位

//映射ID方便运算 
 }int ok(int a,int b,int a1,int b1){return ((a1==b1)||(a1==b &&b1==a)); 任何一个鬼不能占用同一位置+剪枝 
}  
int cx[] = { -1, 1, 0, 0, 0 };int cy[] = { 0, 0, -1, 1, 0 };int id[MAXN + 10][MAXN + 10];int G[MAXM + 10][5];int st[3], ed[3];int edge[MAXM + 10], d[MAXM + 10][MAXM + 10][MAXM + 10];int w, h, n, cnt;char M[MAXN + 10][MAXN + 10];void bfs(){queue q;memset(d, -1, sizeof(d));q.push(getnum(st[0], st[1], st[2])); //看队列前面的元素找最短路径
d[st[0]][st[1]][st[2]] = 0;while (!q.empty()){int u = q.front(); q.pop();int a = (u >> 16) & 255, b = (u >> 8) & 255, c = u & 255;if (a == ed[0] && b == ed[1] && c == ed[2]) return;for (int i = 1; i <= edge[a]; i++){int a1 = G[a][i];for (int j = 1; j <= edge[b]; j++){int b1 = G[b][j];if (ok(a, b, a1, b1))continue;for (int k = 1; k <= edge[c]; k++){int c1 = G[c][k];if (ok(a, c, a1, c1)) continue;if (ok(b, c, b1, c1)) continue;if (d[a1][b1][c1] == -1){d[a1][b1][c1] = d[a][b][c] + 1;q.push(getnum(a1, b1, c1));} //三重循环暴力所有状态}}}}}int main(){//freopen("bu.in","r",stdin);//freopen("bu.out","w",stdout);while (scanf("%d%d%d", &w, &h, &n) && n){char c = getchar();while (c != '\n')c = getchar();for (int i = 1; i <= h; i++)fgets(M[i] + 1, 20, stdin);cnt = 0;int x[MAXM + 10], y[MAXM + 10];for (int i = 1; i <= h; i++)for (int j = 1; j <= w; j++)if (M[i][j] != '#') //把障碍去掉,建图 
{id[i][j] = ++cnt;x[cnt] = i;y[cnt] = j;if ('a' <= M[i][j] && M[i][j] <= 'c')st[M[i][j] - 'a'] = cnt;if ('A' <= M[i][j] && M[i][j] <= 'C')ed[M[i][j] - 'A'] = cnt; //可以用来判断字符c是否为大写英文字母。 
}for (int i = 1; i <= cnt; i++){edge[i] = 0;for (int j = 0; j < 5; j++){int xx = x[i] + cx[j];int yy = y[i] + cy[j];if (M[xx][yy] != '#')
G[i][++edge[i]] = id[xx][yy];}}if (n <= 2){edge[++cnt] = 1;G[cnt][1] = cnt;st[2] = ed[2] = cnt;}if (n <= 1){edge[++cnt] = 1;G[cnt][1] = cnt;st[1] = ed[1] = cnt;} //n==1||n==2时,把格子加上,凑够三个,初末位置重合,所有情况均为三个鬼,方便 
bfs();printf("%d\n", d[ed[0]][ed[1]][ed[2]]);} return 0; }

当然刘汝佳也提到了用双向BFS,双向BFS的话从起点和终点同时双向一层一层地BFS,双向BFS使结点扩展由 a^(x)变为2*a^(x/2)。

//this is my test for that double directions to BFS.
#include
#include
#include
#include
using namespace std;
const int N=17;
const int maxn=200;
const int dx[]={1,0,-1,0,0};
const int dy[]={0,-1,0,1,0};
int n,m,k;
char tomymaze[N][N];
int x[maxn],y[maxn],cnt,mymap[maxn][5],degree[maxn],id[maxn][maxn];
int s[3],t[3];
int d[maxn][maxn][maxn],bd[maxn][maxn][maxn];
int ID(int a,int b,int c){
    return (a<<16)|(b<<8)|c;
}
void init(){
memset(d,-1,sizeof(d));
memset(bd,-1,sizeof(bd));
}
bool forcheck(int a,int b,int aa,int bb){
if(aa==b&&bb==a||aa==bb){
    return true;
}
return false;
}
queue q;
bool frobfs(){
int value;
int uu=q.front();
int aa=(uu>>16)&0xff,bb=(uu>>8)&0xff,cc=uu&0xff;
value=d[aa][bb][cc];
while(!q.empty()){
    int u=q.front();
        int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff;
        if(d[a][b][c]!=value){
                return false;
        }
        if(bd[a][b][c]!=-1){
                return true;
        }
        for(int i=0;i p;
bool behbfs(){
int value;
    int uu=p.front();
    int aa=(uu>>16)&0xff,bb=(uu>>8)&0xff,cc=uu&0xff;
    value=bd[aa][bb][cc];
while(!p.empty()){
        int u=p.front();
        int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff;
        if(bd[a][b][c]!=value){
                return false;
        }
        if(d[a][b][c]!=-1){
                return true;
        }
        for(int i=0;i


你可能感兴趣的:(暴力)