3737. 【NOI2014模拟7.11】挖宝藏(treasure)

Description

“挖矿小子”是一个经典的益智小游戏。玩家需要指挥矿工下地挖掘宝藏,每次矿工可以挖开其左边、右边或下方的格子,不可以挖上方的格子。矿工往下挖矿后就再也不能上来。获得所有宝藏后游戏结束。

这个游戏太简单了,在 \(3 \mathrm{D}\) 版 2048 出现之前若干年,就有人开发出了 \(3 \mathrm{D}\) 版的 “挖矿小
子”。

游戏的地图是一个 \(h \times n \times m\) 的立方体。立方体由上至下共有 \(h\) 层,编号依次为
\(1,2, \ldots, h\),每层是一个 \(n \times m\) 的方阵。你可以假定地面层的编号为 0 。初始时所有单位立
方体 (简称单位) 均有泥土,一些单位有宝藏。挖开单位 \((i, j, k)\) (第 \(i\) 层第 \(j\) 行第 \(k\) 列,\(1 \leq i \leq h, 1 \leq j \leq n, 1 \leq k \leq m\) ) 的泥土消耗的体力为 \(a[i, j, k]\) 。当矿工在单位 \((z, x, y)\)
时,他可以不费体力地获得该单位的宝藏 (如果有的话),同时他可以挖开任一相邻单位(\((z, x-1, y),(z, x+1, y),(z, x, y-1),(z, x, y+1),(z+1, x, y)\))的泥土,如果相邻单位的泥土已经被挖走, 则矿工可以直接到达该单位而不消耗任何体力。矿工不可以到达地图范围之外。和普通版游戏一样, 矿工不可以挖所在位置上方的单位,如果矿工挖开其下方的单位,他会掉落到下一层,并永远不能回到原来所在的层。当矿工获得所有宝藏时,游戏结束,得分为矿工消耗的总体力。

现在你正在玩这个游戏的某一关,在 \(a[i, j, k]\) 和宝藏位置已知的情况下,你需要让你的
矿工顺利地获得所有的宝藏,并使他消耗的体力最少。

Solution

这题是 WC2008 游览计划的加强版,本题中 \(h\) 不再为 1,但我们依旧可以采用同样的方法,用状压 \(\mathrm{DP}\) 来解决此题。

设 \(f_{i,j,k,s}\) 表示到了第 \(i\) 层,在第 \(j\) 行,第 \(k\) 列,宝藏状态为 \(s\)。

先枚举 \(s\) 的子集 \(ss\),转移方程:\(f_{i,j,k,s}=\min(f_{i,j,k,ss}+f_{i,j,k,s-ss}-a_{i,j,k})\)。

然后枚举 \((j,k)\) 周围的单位,转移方程:\(f_{i,j,k,s}=\min(f_{i,x,y,s}+a_{i,j,k})\)。

注意到第 2 个转移方程有后效性,所以用 \(spfa\) 来转移。

至于层与层之间,考虑新建一个节点,表示当前层宝藏全都取完了,然后转移。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 15
#define inf 0X3f3f3f3f
using namespace std;
struct node
{
    int x,y;
};
queue<node> q;
int h,n,m,x,y,ans=0X3f3f3f3f,mp[N][N][N],cnt[N],bz[N][N][N],f[N][N][N][1<<10],fx[5]={0,0,0,1,-1},fy[5]={0,1,-1,0,0};
bool bj[N][N];
void spfa(int i,int s)
{
    while (!q.empty())
    {
        node u=q.front();q.pop();
        for (int k=1;k<=4;++k)
        {
            int x=u.x+fx[k],y=u.y+fy[k];
            if (x<1||x>n||y<1||y>m) continue;
            if (f[i][x][y][s]>f[i][u.x][u.y][s]+mp[i][x][y])
            {
                f[i][x][y][s]=f[i][u.x][u.y][s]+mp[i][x][y];
                if (!bj[x][y])
                {
                    q.push((node){x,y});
                    bj[x][y]=true;
                }
            }
        }
        bj[u.x][u.y]=false;
    }
}
int main()
{
    freopen("treasure.in","r",stdin);
    freopen("treasure.out","w",stdout);
    scanf("%d%d%d",&h,&n,&m);
    for (int i=1;i<=h;++i)
        for (int j=1;j<=n;++j)
            for (int k=1;k<=m;++k)
                scanf("%d",&mp[i][j][k]);
    for (int i=1;i<=h;++i)
    {
        scanf("%d",&cnt[i]);
        for (int j=1;j<=cnt[i];++j)
        {
            scanf("%d%d",&x,&y);
            bz[i][x][y]=j;
        }
        if (i>1) ++cnt[i];
    }
    memset(f,0x3f3f3f3f,sizeof(f));
    for (int i=1;i<=h;++i)
    {
        for (int j=1;j<=n;++j)
            for (int k=1;k<=m;++k)
            {
                if (bz[i][j][k]) f[i][j][k][1<<(bz[i][j][k]-1)]=mp[i][j][k];
                else f[i][j][k][0]=mp[i][j][k];
            }
        for (int s=0;s<(1<<cnt[i]);++s)
        {
            for (int j=1;j<=n;++j)
                for (int k=1;k<=m;++k)
                {
                    for (int ss=s;ss;--ss)
                    {
                        if ((ss|s)!=s) continue;
                        f[i][j][k][s]=min(f[i][j][k][s],f[i][j][k][ss]+f[i][j][k][s-ss]-mp[i][j][k]);
                    }
                    if (f[i][j][k][s]<inf) q.push(node{j,k}),bj[j][k]=true;
                }
            spfa(i,s);
            if (s==(1<<cnt[i])-1)
            {
                for (int j=1;j<=n;++j)
                    for (int k=1;k<=m;++k)
                    {
                        if (f[i][j][k][s]>=inf) continue;
                        if (i==h) ans=min(ans,f[i][j][k][s]);
                        else if (bz[i+1][j][k]) f[i+1][j][k][(1<<(cnt[i+1]-1))|(1<<(bz[i+1][j][k]-1))]=f[i][j][k][s]+mp[i+1][j][k];
                        else f[i+1][j][k][1<<(cnt[i+1]-1)]=f[i][j][k][s]+mp[i+1][j][k];
                    }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
上一篇:查找IDEA 项目中的依赖包存放在.m2位置


下一篇:Codeforces Round #660 (Div. 2) Captain Flint and Treasure 拓扑排序(按照出度、入读两边拓扑排序)