【题解】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;
}