时间限制 : 40000 MS 空间限制 : 165536 KB

问题描述

VRI(Voltron机器人学会)的工程师建造了n个机器人。任意两个兼容的机器人站在同一个格子时可以合并为一个复合机器人。

我们把机器人用1至n编号(1<=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个机器人全部合并(如果可能的话)。

输入格式

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

输出格式

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

样例输入

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

样例输出

5

提示

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

数据范围

2<=n<=9,2<=w<=500且2<=h<=500

题解

一道斯坦纳树好题。特殊点数目很少,特殊点的连接变为了特殊点的移动而已。
本题必须要标号相邻的特殊点才能合并,因此设状态时不能再使用状态压缩。类比区间合并的状态,我们设状态: f[l][r][x][y] f [ l ] [ r ] [ x ] [ y ] <script type="math/tex" id="MathJax-Element-1438">f[l][r][x][y]</script>,表示编号从l到r的机器人在(x,y)处合并的最优值。因此有转移方程:

f[l][r][x][y]=min(f[l][r][x][y],f[l][t][x][y]+f[t+1][r][x][y]) f [ l ] [ r ] [ x ] [ y ] = m i n ( f [ l ] [ r ] [ x ] [ y ] , f [ l ] [ t ] [ x ] [ y ] + f [ t + 1 ] [ r ] [ x ] [ y ] )
<script type="math/tex; mode=display" id="MathJax-Element-1439">f[l][r][x][y]=min(f[l][r][x][y],f[l][t][x][y]+f[t+1][r][x][y])</script>
f[l][r][tx][ty]=min(f[l][r][tx][ty],f[l][r][x][y]+1) f [ l ] [ r ] [ t x ] [ t y ] = m i n ( f [ l ] [ r ] [ t x ] [ t y ] , f [ l ] [ r ] [ x ] [ y ] + 1 )
<script type="math/tex; mode=display" id="MathJax-Element-1440">f[l][r][tx][ty]=min(f[l][r][tx][ty],f[l][r][x][y]+1)</script>前者直接区间合并,后者在SPFA中实现。
由于用到了SPFA转移状态,且本题机器人移动的方式十分特殊,因此我们需要处理处每个格子往四个方向出发到达的位置。我们用DFS对其进行处理,但复杂度爆炸。我们发现 很多点最终的状态都是相同的(因为机器人一口气走到底),因此可以用 记忆化剪枝,特判一下转向器的情况。
然而这样以后还是过不了本题,因为会卡SPFA。这里用到了一个特殊的优化,我们按 f f <script type="math/tex" id="MathJax-Element-1441">f</script>值的大小对状态排序(推荐桶排序)后放入队列 q 1 <script type="math/tex" id="MathJax-Element-1442">q1</script>中,松弛扩展出的点放入 q2 q 2 <script type="math/tex" id="MathJax-Element-1443">q2</script>,这样两个队列中的值都是由小到大排列的(有优先队列的功效),每次松弛时从两个队列前端取出最小的一个值。这样避免了很多重复的讨论。
更多细节见代码。

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef pair<int,int> pii;
const int inf=1e9;
int n,h,w,ans=inf,tot,maxx,cnt[505*505];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
int f[10][10][505][505];
pii v[505*505],q[505*505],map[505][505][4];
bool flag[505][505][4],mark[505][505];
char G[505][505];
queue<pii> q1,q2;
pii DFS(int x,int y,int z)
{
    if(flag[x][y][z]) return map[x][y][z];
    flag[x][y][z]=true;
    int i=z;
    if(G[x][y]=='A') i=(i+3)%4;
    else if(G[x][y]=='C') i=(i+1)%4;
    int tx=dx[i]+x,ty=dy[i]+y;
    if(tx>=1&&tx<=h&&ty>=1&&ty<=w&&G[tx][ty]!='x') map[x][y][z]=DFS(tx,ty,i);
    else map[x][y][z]=make_pair(x,y);
    return map[x][y][z];
}
void spfa(int l,int r)
{
    for(int i=0;i<=maxx;i++) cnt[i]=0;
    for(int i=1;i<=tot;i++) cnt[f[l][r][v[i].first][v[i].second]]++;
    for(int i=1;i<=maxx;i++) cnt[i]+=cnt[i-1];
    for(int i=tot;i;i--) q[cnt[f[l][r][v[i].first][v[i].second]]--]=v[i];
    for(int i=1;i<=tot;i++) q2.push(q[i]);
    while(!q2.empty()||!q1.empty())
    {
        int x,y;
        if(q1.empty()) x=q2.front().first,y=q2.front().second,q2.pop();
        else if(q2.empty())
        {
            x=q1.front().first,y=q1.front().second;
            q1.pop(),mark[x][y]=false;
        }
        else if(f[l][r][q2.front().first][q2.front().second]<f[l][r][q1.front().first][q1.front().second])
        {
            x=q2.front().first,y=q2.front().second,q2.pop();
        }
        else
        {
            x=q1.front().first,y=q1.front().second;
            q1.pop(),mark[x][y]=false;
        }
        for(int i=0;i<4;i++)
        {
            int tx=map[x][y][i].first,ty=map[x][y][i].second;
            if(f[l][r][x][y]+1<f[l][r][tx][ty])
            {
                f[l][r][tx][ty]=f[l][r][x][y]+1;
                if(!mark[tx][ty]) mark[tx][ty]=true,q1.push(make_pair(tx,ty));
            }
        }
    }
}
int main()
{
    memset(f,0x3f,sizeof(f));
    scanf("%d%d%d",&n,&w,&h);
    for(int i=1;i<=h;i++) scanf("%s",G[i]+1);
    for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++)
            if(G[i][j]>=48&&G[i][j]<=57) f[G[i][j]-48][G[i][j]-48][i][j]=0;
    for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++)
            for(int k=0;k<4;k++)
                if(!flag[i][j][k]) map[i][j][k]=DFS(i,j,k);
    for(int k=1;k<=n;k++)
        for(int i=1;i+k-1<=n;i++)
        {
            int j=i+k-1;tot=maxx=0;
            for(int x=1;x<=h;x++)
                for(int y=1;y<=w;y++)
                {
                    for(int t=i;t<j;t++)
                        f[i][j][x][y]=min(f[i][j][x][y],f[i][t][x][y]+f[t+1][j][x][y]);
                    if(f[i][j][x][y]<inf) maxx=max(f[i][j][x][y],maxx),v[++tot]=make_pair(x,y);
                }
            spfa(i,j);
        }
    for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++) ans=min(ans,f[1][n][i][j]);
    if(ans==inf) puts("-1");
    else printf("%d\n",ans);
    return 0;
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐