【JZOJ3737】【NOI2014模拟7.11】挖宝藏(treasure) 状压DP+斯坦纳树+SPFA

任务

这里写图片描述
这里写图片描述

解法

考虑二维时的情况,
可以发现是对指定点的最小生成树,被称为斯坦纳树
注意到点数很少,
我们可以利用状态压缩Dp求解。
f[i][j][s] 表示,以 (i,j) 为根,连通状态为 s 的最小代价。
显然有,

f[i][j][s]={f[i][j][s]+f[i][j][ss],f[i][j][s]+a[i][j],ss(i,j)(i,j)

注意到,
用①转移时,由于 s<s ,所以 f[][][s] 是已经算好了的,所以可以直接转移。
但是我们发现在用②转移时,显然有可能会出现环状更新的情况。
普通的递推显然不太适用;
但由于满足三角不等式关系,所以可以变形为SPFA来进行转移。

总体来说,我们先进行①转移,把所有可以更新的点加入队列中,然后再统一进行SPFA
二维的情况就解决了。


接下来我们考虑三维的情况;
我们需要一种可以层与层之间递推的方法。
我们给每一层额外设置一个特殊的宝藏,只有当付出了前面层的代价,才能获得这个宝藏。
那么就可以处理出三维的情况了。

代码

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<math.h>
#define ll long long
using namespace std;
const char* fin="treasure.in";
const char* fout="treasure.out";
const ll inf=0x7fffffff;
const ll maxn=11,Maxn=107,maxm=Maxn*80,maxk=1<<10;
const ll w[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
ll N,n,m,i,j,k,x,y,l;
ll a[maxn][maxn][maxn],b[maxn][maxn*maxn][2],c[maxn];
ll f[maxn][maxn][maxn][maxk];
ll trea[maxn][maxn][maxn];
ll head,tail,B[maxn*maxn*800][2];
bool bz[maxn][maxn];
void add_tail(ll x,ll y){
    B[++tail][0]=x;
    B[tail][1]=y;
    bz[x][y]=true;
}
void add(ll z,ll x,ll y,ll st,ll d){
    if (f[z][x][y][st]>d){
        f[z][x][y][st]=d;
        if (!bz[x][y]) add_tail(x,y);
    }
}
void spfa(ll sz,ll st){
    ll i,j,k;
    while (head++<tail){
        for (i=0;i<4;i++){  
            j=B[head][0]+w[i][0];
            k=B[head][1]+w[i][1];
            if (j>0 && j<=n && k>0 && k<=m) add(sz,j,k,st,f[sz][B[head][0]][B[head][1]][st]+a[sz][j][k]);
        }
        bz[B[head][0]][B[head][1]]=false;
    }
    head=tail=0;
}
int main(){
    freopen(fin,"r",stdin);
    freopen(fout,"w",stdout);
    scanf("%lld%lld%lld",&N,&n,&m);
    for (i=1;i<=N;i++) for (j=1;j<=n;j++) for (k=1;k<=m;k++) scanf("%lld",&a[i][j][k]);
    for (i=1;i<=N;i++){
        scanf("%lld",&c[i]);
        for (j=1;j<=c[i];j++){
            scanf("%lld%lld",&b[i][j][0],&b[i][j][1]);
            trea[i][b[i][j][0]][b[i][j][1]]=j;
        }
        if (i>1) c[i]++;
    }
    ll ans=inf;
    memset(f,127,sizeof(f));
    for (i=1;i<=N;i++){
        for (j=1;j<=n;j++)
            for (k=1;k<=m;k++)
                if (trea[i][j][k]) f[i][j][k][1<<(trea[i][j][k]-1)]=a[i][j][k];
                else f[i][j][k][0]=a[i][j][k];
        for (l=0;l<1<<c[i];l++){
            for (j=1;j<=n;j++)
                for (k=1;k<=m;k++){
                    for (ll L=l&(l-1);L;L=l&(L-1)){
                        if (f[i][j][k][L]<2000000000 && f[i][j][k][l-L]<2000000000)
                            f[i][j][k][l]=min(f[i][j][k][l],f[i][j][k][L]+f[i][j][k][l-L]-a[i][j][k]);
                    }
                    if (f[i][j][k][l]<2000000000) add_tail(j,k);
                }
            spfa(i,l);
            if (l==(1<<c[i])-1)
                for (j=1;j<=n;j++) for (k=1;k<=m;k++){
                    if (f[i][j][k][l]>2000000000) continue;
                    if (i==N) ans=min(ans,f[i][j][k][l]);
                    else if (trea[i+1][j][k])
                        f[i+1][j][k][(1<<(c[i+1]-1))|(1<<(trea[i+1][j][k]-1))]=a[i+1][j][k]+f[i][j][k][l];
                    else f[i+1][j][k][(1<<(c[i+1]-1))]=a[i+1][j][k]+f[i][j][k][l];
                }
        }
    }
    printf("%lld",ans);
    return 0;
}

Warning

1.在往下一层转移时,事实上我错误地认为一定要连带前面层的代价,于是就处理不了一些情况。
2.同层使用SPFA时,不必规避遇到黑点的情况。
3.小技巧,
枚举某二进制数的真子集:

for (i=j;i;i=j&(i-1))

大致思路就是每次都把最后一位1删掉,
然后等到变为1000…000这样的形式之后,就会缩短一位,重新枚举。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值