【jzoj3737】【挖宝藏】【斯坦纳树】【状态压缩动态规划】

题目大意

这里写图片描述

解题思路

每层单独考虑,就是平面图的最小生成树,斯坦纳树。设F[i][j][s]表示当前在(i,j)目标点的选取情况为s的最小花费,观察可知当前状态可以由邻近节点走一步得来,也可以由当前节点s的两个子状态得来,注意减去重复的当前点的花费。我们可以枚举s,先更新所有点s的答案,再通过spfa更新其他点的答案。
对于多层,考虑先把下一层解决,把下一层当作一个目标点加在当前层上,每个点都要这样做,这样就可以解决从当前层走到最后的花费,问题就可以解决了。

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LF double
#define LL long long
#define Min(a,b) ((a<b)?a:b)
#define Max(a,b) ((a>b)?a:b)
#define Fo(i,j,k) for(int i=j;i<=k;i++)
#define Fd(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
int const Mxn=10,Mxa=1<<10,Mxsi=1e4,Inf=1e9;
int H,N,M,A[Mxn+9][Mxn+9][Mxn+9],F[Mxn+9][Mxn+9][Mxa+9],
    B[Mxn+9][Mxn+9][2],Q[Mxsi+9][2],Inq[Mxn+9][Mxn+9],K[Mxn+9],
    W[4][2]={{-1,0},{0,-1},{0,1},{1,0}},G[Mxn+9][Mxn+9];
int main(){
    //freopen("treasure.in","r",stdin);
    //freopen("treasure.out","w ",stdout);
    freopen("d.in","r",stdin);
    freopen("d.out","w ",stdout);
    scanf("%d%d%d",&H,&N,&M);
    Fo(i,1,H)Fo(j,1,N)Fo(k,1,M)scanf("%d",&A[i][j][k]);
    //Fo(j,1,N)Fo(k,1,M)Fo(l,1,Mxa)F[j][k][l]=Inf;
    Fo(i,1,H){
        scanf("%d",&K[i]);
        Fo(j,1,K[i])scanf("%d%d",&B[i][j][0],&B[i][j][1]);
    }
    Fd(i,H,1){
        K[i]++;int Mxs=(1<<K[i])-1;
        Fo(j,1,N)Fo(k,1,M){
            F[j][k][0]=A[i][j][k];
            Fo(l,1,Mxs)F[j][k][l]=Inf;
            F[j][k][1<<(K[i]-1)]=G[j][k]+A[i][j][k];
        }
        Fo(j,1,K[i]-1)F[B[i][j][0]][B[i][j][1]][1<<(j-1)]=A[i][B[i][j][0]][B[i][j][1]];
        //Fo(j,1,K)scanf("%d%d",&B[j][0],&B[j][1]),F[B[j][0]][B[j][1]][1]=F[B[j][0]][B[j][1]][0];
        Fo(s,1,Mxs){
            int He=0,Ti=0;
            for(int ss=s;ss;ss=(ss-1)&s)Fo(j,1,N)Fo(k,1,M)
                F[j][k][s]=Min(F[j][k][s],F[j][k][ss]+F[j][k][s-ss]-A[i][j][k]);
            //Fo(j,1,K)F[B[j][0]][B[j][1]][s]=F[B[j][0]][B[j][1]][s^(1<<(j-1))];
            Fo(j,1,N)Fo(k,1,M)Q[++Ti][0]=j,Q[Ti][1]=k,Inq[j][k]=1;
            while(He!=Ti){
                int X=Q[++He][0],Y=Q[He][1];
                Fo(l,0,3)if(F[X+W[l][0]][Y+W[l][1]][s]>F[X][Y][s]+A[i][X+W[l][0]][Y+W[l][1]]){
                    F[X+W[l][0]][Y+W[l][1]][s]=F[X][Y][s]+A[i][X+W[l][0]][Y+W[l][1]];
                    if(!Inq[X+W[l][0]][Y+W[l][1]]){
                        Inq[X+W[l][0]][Y+W[l][1]]=1;
                        Q[++Ti][0]=X+W[l][0];
                        Q[Ti][1]=Y+W[l][1];
                        if((Q[Ti][0]<1)||(Q[Ti][1]<1)){
                            int bb;
                            bb++;
                        }
                    }
                }
                Inq[X][Y]=0;
            }
        }
        Fo(j,1,N)Fo(k,1,M)G[j][k]=F[j][k][Mxs];
    }
    int Ans=Inf;
    Fo(j,1,N)Fo(k,1,M)Ans=Min(Ans,G[j][k]);
    printf("%d",Ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值