【题解】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\begin{cases}f_{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}\end{cases}\)

\(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=0}C^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;
}

原文地址:https://www.cnblogs.com/opethrax/p/11333963.html

时间: 2024-08-08 13:53:25

【题解】JZOJ 提高A组 19.8.10 挖宝藏的相关文章

【模拟赛】纪中提高A组 19.8.9 测试

Task.1 走格子 题目大意:C和F在一个可以看作 \(N\times M\) 的矩阵的房间中,矩阵中的每一个元素可以是: 障碍:"#" C或者F的起点:"C"或"F" 空区域:"." C携带了一把传送枪,每次C都可以: 花费一个单位时间移动到相邻的空区域 不花费时间向上下左右之一的方向的墙壁上发射传送门(传送门最多只能同时存在两扇,如果已经存在两扇再发射一扇那最早出现的那扇会消失,一个位置不能存在两扇传送门) 花费一个单位

【模拟赛】纪中提高A组 19.8.17 测试

Task.1 倾斜的线 题目大意:在平面上有 \(n\) 个点,给定 \(P,Q\),在平面上找到两个点使它们所在直线的斜率数值上最接近 \(\frac{P}{Q}\) .对于接近程度相同的直线选择较小的斜率. 数据范围:\(2\leq N\leq 2\times 10^5,1\leq P,Q,x,y\leq 10^9\),不存在经过两点的直线斜率为 \(0\) 或正无穷. 在考场想到了一个没那么naive的想法,把坐标系旋转到 \(y\) 轴平行于斜率为 \(\frac{P}{Q}\) 的直线

[部分题解]noip2013提高组Day2

积木大赛: 之前没有仔细地想,然后就直接暴力一点(骗点分),去扫每一高度,连到一起的个数,于是2组超时 先把暴力程序贴上来(可以当对拍机) 1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 FILE *fin = fopen("block.in","r"); 5 FILE *fout= fopen("block.out","w&quo

[题解]noip提高组

1.无线网络发射器选址 这道题数据范围很小,就直接暴力枚举就好了.为了提高速度,就从每个有公共场所的点枚举周围在(x,y)放无线网路 发射器可以增加的公共场所数量,加到一个数组里.所有公共场所都处理完了后,把这个数组扫一遍,边扫边得到最大值和 个数 Code: 1 #include<iostream> 2 #include<fstream> 3 #include<map> 4 using namespace std; 5 ifstream fin("wirel

[部分题解]noip2012提高组day2

这道题有多种解法,我用的是扩展欧几里得算法求到的答案 1 #include<iostream> 2 #include<fstream> 3 #include<cstdio> 4 using namespace std; 5 typedef long long ll; 6 ifstream fin("mod.in"); 7 FILE *fout = fopen("mod.out","w"); 8 void gcd

2018.8.10提高B组模拟考试

为了迎合今天的讲课内容——数论,A组和B组都各出了两道数学. 对于完全不会数论的博主来说,这简直是灾难. T1 题意简述:jzoj5791 解题思路:看到这道题,首先想到对n个数分别分解成质数后存在数组里. 然后呢?枚举ans吗? 其实可以二分答案,加上一个求质数个数的技巧就能过. 发现cnt[2]=ans/2+ans/4+ans/8+... cnt[3]=ans/3+ans/9+ans/27+... 本题结束. #include<iostream> #include<cstdio>

【NOIP2016提高A组模拟10.15】最大化

题目 分析 枚举两个纵坐标i.j,接着表示枚举区域的上下边界, 设对于每个横坐标区域的前缀和和为\(s_l\),枚举k, 显然当\(s_k>s_l\)时,以(i,k)为左上角,(j,k)为右下角的矩阵一定合法. k从小到大,维护一个单调队列, 显然当\(l1<l2\)时 如果\(s_{l1}<s_{l2}\),l2一定对答案没有贡献,就不将其加入单调队列. 对于一个k,在单调队列中二分,枚举出一个最小的位置,并且\(s_k>s_l\). #include <iostream&

【NOIP2016提高A组集训第3场10.31】高维宇宙

题解 分析 因为只有奇数和偶数配对才有可能得出质数, 暴力求出每一对\(a_i+a_j\)为质数,将其中的奇数想偶数连一条边. 二分图匹配,匈牙利算法. #include <cmath> #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <queue> const

【NOIP2017提高组模拟12.10】幻魔皇

题目 幻魔皇拉比艾尔很喜欢斐波那契树,他想找到神奇的节点对. 所谓斐波那契树,根是一个白色节点,每个白色节点都有一个黑色节点儿子,而每个黑色节点则有一个白色和一个黑色节点儿子.神奇的节点对则是指白色节点对. 请问对于深度为n的斐波那契树,其中距离为i的神奇节点对有多少个?拉比艾尔需要你对于1<=i<=2n的所有i都求出答案. 分析 我们找一找每层黑点和白点的规律 |层数|白点数|黑点数| |:-|:---|:----| |1|1|0| |2|0|1| |3|1|1| |4|1|2| |5|2|