【题解】JZOJ3737 提高A组 NOI2014.7.11模拟 19.8.10 挖宝藏
Posted on 2019-08-11 09:41 opethrax 阅读(166) 评论(1) 编辑 收藏 举报二进制枚举子集
先给出代码:
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;
}