bzoj3205 [Apio2013]机器人

时间:2021-05-26 06:33:56

3205: [Apio2013]机器人

Time Limit: 15 Sec  Memory Limit: 128 MB
Submit: 953  Solved: 227
[Submit][Status][Discuss]

Description

VRI(Voltron机器人学会)的工程师建造了 n个机器人。任意两个兼容的机器人站在同一个格子时可以合并为一个复合机器人。我们把机器人用 1至 n编号(n ≤ 9)。如果两个机器人的编号是连续的,那么它们是兼容的,可以合并成一个复合机器人。最初这   n   个机器人各自都只有唯一的编号。而一个由两个或以上的机器人合并构成的复合机器人拥有两个编号,分别是构成它的所有机器人中最小和最大的编号。例如, 2号机器人只可以与 1号或 3号机器人合并。若 2号机器人与 3号机器人合并,可构成编号为 2-3的复合机器人。如果编号为 2-3的复合机器人与编号为 4-6的复合机器人合并,可构成编号为 2-6的复合机器人。当所有机器人合并以后则构成 1-n复合机器人。工程师把这 n个机器人放在了一个封闭的房间中,房间四周均是墙。该房间被划分成 w     h    个方格。有些方格有障碍物,机器人不可经过或停留;其余方格允许多个机器人停留,同时允许机器人经过。任何时候一个机器人只占用一个方格。初始时刻,所有机器人均在不同的方格中。这些原始的机器人不会自发地移动。它们只有被工程师沿   x轴或 y轴推动后,才会沿推动的方向不断向前直线移动,直至碰到障碍物或墙停止移动。停止移动后,它会扫描当前的格子是否存在可以与它合并的机器人,如果有,则合并并继续检查,直至不能再合并为止。工程师只能沿水平向左、水平向右、竖直向上、竖直向下四个方向推动机器人,并且,在机器人尚未停止移动时,不允许推动其它机器人,因此任何时刻,房间中都只能有一个机器人移动,为了帮助机器人转向,工程师在一些格子中放置了转向器。具体地说,转向器分为顺时针转向器(右转器)和逆时针转向器(左转器),顺时针转向器可以使到达该格子的机器人沿顺时针方向转向   90_;逆时针转向器可以使到达该格子的机器人沿逆时针方向转向 90_。现在,我们将告诉你初始时刻房间内的信息。请你计算工程师最少共计需要推动机器人多少次,才能把所有的 n个机器人全部合并(如果可能的话)。

Input

你的程序必须从标准输入读入。输入的第 1行包含 3个整数 n、w和 h,用空格隔开。输入文件中接下来的 h行描述初始时刻房间内的信息,每行包含w个字符。这w* h 字符中每一个表示房间中的一个格子,意义如下:
 
‘ 1’至‘9’:表示该方格中有一个机器人,编号为这个数字;
‘ x’:表示该方格有障碍物;
 
‘ A’:表示该方格中有一个逆时针转向器;
 
‘ C’:表示该方格中有一个顺时针转向器;
‘ .’:表示该方格为空地。

Output

你的程序必须输出到标准输出。输出仅一个整数,表示最少需要推动的次数。
若不能使所有机器人全部合并,输出-1。

Sample Input

4 10 5
1.........
AA...x4...
..A..x....
2....x....
..C.3.A...

Sample Output

5

HINT

第一步:向右推动 3 号机器人,当它碰到转向器后会向上继续移动,直至碰到墙壁停止移动。第二步:向上推动 4 号机器人,当它碰到墙壁后停止移动,与3 号机器人合并,构成  3-4 号机器人 第三步:向上推动 2 号机器人,当它碰到转向器后会向左移动,由于左侧为墙壁,故停留在原地。第四步:向右推动  2 号机器人,由于它在一个转向器上,故它会向上移动,直至碰到墙壁停止移动,与  1 号机器人合并,构成 1-2 号机器人。第五步:向左推动  3-4 号机器人,当它碰到墙壁后停止移动,与 1-2 号机器人合并,构成  1-4 号机器人。

≤ 9,≤ 500 且   h ≤ 500

分析:非常坑的一道题.

   这道题是斯坦纳树模型一眼就能看出来,令dp[l][r][x][y]表示编号为l~r的机器人所在(x,y)这个位置时的最少推动次数。 有dp[l][r][x][y]=min{dp[l][k][x][y]+dp[k+1][r][x][y]}或者dp[l][r][x][y]由其它dp[l][r][x1][y1]而来。 后者可以通过SPFA来解决。可以先预处理出每个位置朝4个方向推动最终会走到哪,用记忆花搜索处理.

   然后就到了最麻烦的地方了,普通的spfa算法会超时.注意到这道题的边权都是1,可以想到用bfs,但是这道题必须要用spfa转移,于是有了一个奇怪的优化:开两个队列q1,q2,一开始将所有点的距离排序后从小到大插入到q2中,每次取q1,q2队首较小的元素扩展,扩展得到的元素加入q1中.因为每次扩展元素大小只会+1嘛,所以q1和q2总是单调的.

   在spfa之前要对元素进行排序.这里的一个优化是计数排序.因为值域很小嘛,比sort要快.

   有上面几个优化还不够,每次清空vis数组会浪费大量的时间,因此还必须用时间戳优化.

   凭着四重优化:记忆化预处理,计数排序,“单调队列”的spfa,时间戳,终于能A掉此题了.get到不少新技能啊.

   犯了一个sb错误:记忆化搜索预处理的时候vis数组开成了两维的.因为方向也是一个影响量嘛,所以要开成三维的,以后要注意了.

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; const int inf = 0x7ffffff,dx[] = {-,,,},dy[] = {,-,,}; struct node
{
int x,y;
} pos[][][],q[ * ],tmp[ * ]; int num,n,m,f[][][][],vis[][][],dfs_clock,sx,sy,sizee,ans = inf,Time,rk[ * ];
int vis2[ * ],cnt[ * ],vis3[][],Time2;
char s[][]; void add(int x,int v,int c)
{
if (vis2[x] != c)
{
vis2[x] = c;
cnt[x] = ;
}
cnt[x] += v;
} int query(int x,int c)
{
if (vis2[x] != c)
{
vis2[x] = c;
cnt[x] = ;
}
return cnt[x];
} void sortt()
{
int maxx = ;
Time++;
for (int i = ; i <= sizee; i++)
{
node temp = q[i];
add(f[sx][sy][temp.x][temp.y],,Time);
maxx = max(maxx,f[sx][sy][temp.x][temp.y]);
}
for (int i = ; i <= maxx; i++)
add(i,query(i - ,Time),Time);
for (int i = ; i <= sizee; i++)
{
rk[i] = query(f[sx][sy][q[i].x][q[i].y],Time);
add(f[sx][sy][q[i].x][q[i].y],-,Time);
}
for (int i = ; i <= sizee; i++)
tmp[rk[i]] = q[i];
for (int i = ; i <= sizee; i++)
q[i] = tmp[i];
} bool check(int x,int y)
{
if (x <= || x > n || y <= || y > m)
return false;
if (s[x][y] == 'x')
return false;
return true;
} void spfa()
{
queue <node> q1,q2;
for (int i = ; i <= sizee; i++)
{
q2.push(q[i]);
vis3[q[i].x][q[i].y] = Time2;
}
while (!q1.empty() || !q2.empty())
{
node u;
if (q1.empty())
{
u = q2.front();
q2.pop();
}
else if (q2.empty())
{
u = q1.front();
q1.pop();
}
else
{ int t1 = f[sx][sy][q1.front().x][q1.front().y],t2 = f[sx][sy][q2.front().x][q2.front().y];
if (t1 < t2)
{
u = q1.front();
q1.pop();
}
else
{
u = q2.front();
q2.pop();
}
}
vis3[u.x][u.y] = ;
int x = u.x,y = u.y;
for (int i = ; i <= ; i++)
{
node temp = pos[x][y][i];
if (temp.x == - && temp.y == -)
continue;
int nx = temp.x,ny = temp.y;
if (check(nx,ny))
{
if (f[sx][sy][nx][ny] > f[sx][sy][x][y] + )
{
f[sx][sy][nx][ny] = f[sx][sy][x][y] + ;
if (vis3[nx][ny] != Time2)
{
vis3[nx][ny] = Time2;
node temp;
temp.x = nx;
temp.y = ny;
q1.push(temp);
}
}
}
}
}
} node dfs(int x,int y,int z)
{
if (vis[x][y][z] == dfs_clock)
{
node temp;
temp.x = temp.y = -;
return temp;
}
if (!(pos[x][y][z].x == && pos[x][y][z].y == ))
return pos[x][y][z];
vis[x][y][z] = dfs_clock;
int nowz = z;
if (s[x][y] == 'A')
nowz = (z + ) % ;
if (s[x][y] == 'C')
nowz = (z + ) % ;
int nx = x + dx[nowz],ny = y + dy[nowz];
if (!check(nx,ny))
{
node temp;
temp.x = x;
temp.y = y;
return temp;
}
return pos[x][y][z] = dfs(nx,ny,nowz);
} int main()
{
scanf("%d%d%d",&num,&m,&n);
for (int i = ; i <= num; i++)
for (int j = i; j <= num; j++)
for (int k = ; k <= n; k++)
for (int l = ; l <= m; l++)
f[i][j][k][l] = inf;
for (int i = ; i <= n; i++)
{
scanf("%s",s[i] + );
for (int j = ; j <= m; j++)
{
if (s[i][j] >= '' && s[i][j] <= '')
{
int id = s[i][j] - '';
f[id][id][i][j] = ;
}
}
}
for (int i = ; i <= n; i++)
for (int j = ; j <= m; j++)
for (int k = ; k <= ; k++)
{
node temp = pos[i][j][k];
if (!(temp.x == && temp.y == ))
continue;
if (s[i][j] == 'x')
continue;
++dfs_clock;
pos[i][j][k] = dfs(i,j,k);
}
for (int len = ; len <= num; len++)
{
for (int i = ; i + len - <= num; i++)
{
int j = i + len - ;
for (int p = i; p < j; p++)
{
for (int k = ; k <= n; k++)
{
for (int l = ; l <= m; l++)
{
f[i][j][k][l] = min(f[i][j][k][l],f[i][p][k][l] + f[p + ][j][k][l]);
}
}
}
sizee = ;
for (int k = ; k <= n; k++)
for (int l = ; l <= m; l++)
if (f[i][j][k][l] != inf)
{
node temp;
temp.x = k;
temp.y = l;
q[++sizee] = temp;
}
sx = i;
sy = j;
sortt();
Time2++;
spfa();
}
}
for (int i = ; i <= n; i++)
for (int j = ; j <= m; j++)
ans = min(ans,f[][num][i][j]);
if (ans == inf)
puts("-1");
else
printf("%d\n",ans); return ;
}