zoukankan      html  css  js  c++  java
  • 【题解】JZOJ3737 提高A组 NOI2014.7.11模拟 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;
    }
    
  • 相关阅读:
    vue自定义指令
    ZOJ Problem Set–2104 Let the Balloon Rise
    ZOJ Problem Set 3202 Secondprice Auction
    ZOJ Problem Set–1879 Jolly Jumpers
    ZOJ Problem Set–2405 Specialized FourDigit Numbers
    ZOJ Problem Set–1874 Primary Arithmetic
    ZOJ Problem Set–1970 All in All
    ZOJ Problem Set–1828 Fibonacci Numbers
    要怎么样调整状态呢
    ZOJ Problem Set–1951 Goldbach's Conjecture
  • 原文地址:https://www.cnblogs.com/opethrax/p/11333963.html
Copyright © 2011-2022 走看看