题解JZOJ 提高A组 19.8.10 挖宝藏

Posted opethrax

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解JZOJ 提高A组 19.8.10 挖宝藏相关的知识,希望对你有一定的参考价值。

二进制枚举子集

先给出代码:

for(int o = s; o; o = (o - 1) & s)

其中\(s\)为当前的状态,\(o\)为枚举的子集。根据与运算的性质我们得到的显然是s的子集,但是为什么这样做可以得到\(s\)所有的子集?

网上的一种说法是把状态\(s\)看做忽略\(0\)的二进制数,只考虑每次对这个二进制数减一,过程大概是:

假如 \(s=(0101101)_2\quad s_0=(1111)_2\)

\(s_1=(s_0-1)\&s=(1110)_2\)

\(s_2=(s_1-1)\&s=(1101)_2\)

\(s_3=(s_2-1)\&s=(1100)_2\)

\(...\)

因为\(s_0\)是一个所有位都为\(1\)的二进制数,所以与\(s_0\)不会对答案造成影响。很明显,去掉了与之后\(s_i\)每次都减一,这样一定可以取到\([0,s_0]\)内的所有状态。

\(0\)可以被忽略的原因就是在与运算下原来为\(0\)的位不管怎么做都不会变成\(1\)影响枚举。这段代码的复杂度是\(s\)的子集数。

挖宝藏

一个矿工在一个三维立方体\((h\times n\times m)\)中挖矿。每个格子内有一个挖掘需要的体力\(a_i,j,k\),初始时都没有挖过;矿工只能挖开 前后左右下 几个方向的格子(挖开下方的格子会掉下去),不能挖上一层的格子;矿工只能移动到 前后左右下 几个方向已经挖开的格子中,但不能回到上一层,移动不消耗体力。矿工的起点在地面上(最上层的上方)。现在指定一些格子有宝藏,到达有宝藏的格子后获得宝藏不需要耗费体力。求矿工得到所有宝藏的最小体力。

这道题的弱化版是 \(WC2008\) 游览计划,只有二维的情况。

题意大概就是是在一张图中,可以选没有指定的点,求令指定的点联通的最小代价。

然后我们在点和点之间连边,边权就是挖开终点的代价。最后的结果我们选择的点构成的图中一定不会有环(一定不优)且联通,最优解一定是一棵树的形态。

这个问题在组合优化学科中被称为斯坦纳树问题,求解本题最优解的方法便是求斯坦纳树的方法。

(这个东西网上有很多juju写过,能翻到这里应该是把他们的博客都看过的人,不加赘述)

套路就是状态压缩DP,设 \(f_x,s\) 表示以\(x\)为根时选择了指定点的集合状态为\(s\)的最小代价。

转移就是把同一个根的两个状态的两颗树接起来,或者把同一个状态的两个根的两颗树接起来。

具体:

\(f_x,s=min\f_x,s_1+f_x,s_2\\quad s_1\bigcup s_2=s\quad s_1\bigcap s_2=\x\\)\(x\)若不是指定点就是是一个空集)

\(f_x,s=min\f_x,s+f_y,s+val_x,y\\quad (x,y)\in E\)

第二个方程有后效性,我们不知道一条边应该\(x\)更新\(y\)还是应该\(y\)更新\(x\),有后效性。不过仔细一看这个东西长得有点像最短路?我们用最短路算法松弛。

本题的状态:\(f_i,x,y,s\) 表示以\((i,x,y)\)为根选了\(s\)中的点的最小代价。

因为是三维的,我们从最底层往上走,每次做完一层把这一层的所有宝藏合成一个放在上一层中。

转移:

\(f_i,x,y,s=min\f_i,x,y,s_1+f_i,x,y,s_2-a_i,j,k\\quad s_1\bigcup s_2=s\quad s_1\bigcap s_2=\(i,x,y)\\)

\(f_i,x,y,s=min\begincasesf_i,x-1,y,s+a_i,x,y\\f_i,x+1,y,s+a_i,x,y\\f_i,x,y-1,s+a_i,x,y\\f_i,x,y+1,s+a_i,x,y\endcases\)

\(f_i,x,y,1=f_i+1,x,y,all+a_i,x,y\)\(1\)表示选了上一层所有宝藏,\(all\)表示上一层所有宝藏的状态)

复杂度:\(O(hmn3^k)\)\(3^k\)的复杂度是根据二项式定理得来的 \(\sum^k_i=0C^i_k\times 2^k\times 1^k-i=(1+2)^k\)

代码:(SPFA版)

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;

template<class T>void read(T &x)
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9')x=(x<<1)+(x<<3)+(c^48); c=getchar();

const int N=13;
int h,n,m,ans=0x7f7f7f7f;
int a[N][N][N];
int f[N][N][N][1050];
int all[N];
struct stat
    int x,y;
    stat(int a1=0,int a2=0)x=a1; y=a2;
;
queue<stat>q;
bool vis[N][N];

int dx[8]=0,0,1,-1;
int dy[8]=1,-1,0,0;

void spfa(int d,int s)
    memset(vis,0,sizeof(vis));
    stat now; int x,y,nx,ny;
    while(!q.empty())
        now=q.front(); q.pop();
        x=now.x; y=now.y; vis[x][y]=0;
        for(int i=0;i<4;i++)
            nx=x+dx[i]; ny=y+dy[i];
            if(nx<1||ny<1||nx>n||ny>m) continue;
            if(f[d][nx][ny][s]>f[d][x][y][s]+a[d][nx][ny])
                f[d][nx][ny][s]=f[d][x][y][s]+a[d][nx][ny];
                if(!vis[nx][ny]) vis[nx][ny]=1; q.push(stat(nx,ny));
            
        
    


int main()
//  freopen("treasure.in","r",stdin);
//  freopen("treasure.out","w",stdout);
    read(h); read(n); read(m);
    for(int i=1;i<=h;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=m;k++) read(a[i][j][k]);
    int sx=0,sy=0;
    memset(f,0x2f,sizeof(f));
    for(int i=1,t,x,y;i<=h;i++)
        read(t); all[i]=0-(i==h);
        while(t--)
            read(x); read(y);
            if(!sx)sx=x; sy=y;
            f[i][x][y][1<<(++all[i])]=a[i][x][y];
        
        all[i]=(1<<(all[i]+1))-1;
    
    int t1,t2,now;
    for(int i=h;i;i--)
        now=all[i];
        for(int s=1;s<=now;s++)
            for(int x=1;x<=n;x++)
                for(int y=1;y<=m;y++)
                    for(int o=s;o;o=(o-1)&s)
                        if((t1=f[i][x][y][o])!=0x2f2f2f2f)
                            if((t2=f[i][x][y][s-o])!=0x2f2f2f2f)
                                f[i][x][y][s]=min(f[i][x][y][s],t1+t2-a[i][x][y]);
                    if(f[i][x][y][s]!=0x2f2f2f2f) q.push(stat(x,y));
                
            spfa(i,s);
        
        for(int x=1;x<=n;x++)
            for(int y=1;y<=m;y++)
                f[i-1][x][y][1]=f[i][x][y][now]+a[i-1][x][y];
    
    printf("%d\n",f[1][sx][sy][all[1]]);
    return 0;

以上是关于题解JZOJ 提高A组 19.8.10 挖宝藏的主要内容,如果未能解决你的问题,请参考以下文章

Jzoj4742NOIP2016提高A组模拟9.2快速幂单峰

jzoj2017.8.21提高组A

[jzoj 5770]2018提高组模拟A组8.6可爱精灵宝贝 (区间dp)

Jzoj 4889NOIP2016提高A组集训第14场11.12暴力贪心最长公共回文子序列

jzoj 6302. 提高组

2017.07.16【NOIP提高组】模拟赛B组 卫星照片 题解