题解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快速幂单峰
[jzoj 5770]2018提高组模拟A组8.6可爱精灵宝贝 (区间dp)