【JZOJ 3737】挖宝藏

Description

这里写图片描述

Solution

这题是关于斯坦纳树的;
先来考虑一下当h=1的情况:
我们发现,最后的答案构建出来一定是一颗树,
那么就设DP方程: fx,y,S 表示以x,y这个点为根的树联通了集合S的点,
那么它可以由以下的数转移过来:(a为代价)

min(fx1,y,S,fx+1,y,S,fx,y1,S,fx,y+1,S,)

min{fx,y,S+fx,y,SSax,y(SS)}

从小到大枚举S,对于每一个位置,更新完以后用SPFA更新其它点,

h>1 时,可以在压缩状态时多压缩一位,表示有没有从上一层转移过来

复杂度:枚举子集的复杂度是: S=ji=12jCij
O(hnm(S+SPFA))

Code

标称SB的用了两个数组而不是多用一个二进制QAQ,辣鸡

#include <cstdio>
#include <cstdlib>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define OK(q,w) ((q)>0&&(w)>0&&(q)<=n&&(w)<=m)
#define min(q,w) ((q)<(w)?(q):(w))
using namespace std;
const int N=12,M=1025;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,h,m1,ans,FAIL;
int a[N][N][N];
int b[N][N];
int er[11];
int f[N][N][M][2],F[N][N];
int d[M*M][3];
bool z[N][N][2];
int FX[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
void SPFA(int e,int I)
{
    int S=1,T=0;
    fo(i,1,n)fo(j,1,m)fo(k,0,1)if(f[i][j][e][k]<FAIL)d[++T][0]=i,d[T][1]=j,d[T][2]=k,z[i][j][k]=1;
    int q,w,t;
    while(S<=T)
    {
        q=d[S][0],w=d[S][1],t=d[S][2];
        fo(k,0,3)if(OK(q+FX[k][0],w+FX[k][1]))
        {
            int q1=q+FX[k][0],w1=w+FX[k][1];
            if(f[q1][w1][e][t]>f[q][w][e][t]+a[I][q1][w1])
            {
                f[q1][w1][e][t]=f[q][w][e][t]+a[I][q1][w1];
                if(!z[q1][w1][t])z[q1][w1][t]=1,d[++T][0]=q1,d[T][1]=w1,d[T][2]=t;
            }
            if(!t)if(f[q1][w1][e][1]>f[q][w][e][t]+a[I][q1][w1]+F[q1][w1])
            {
                f[q1][w1][e][1]=f[q][w][e][t]+a[I][q1][w1]+F[q1][w1];
                if(!z[q1][w1][1])z[q1][w1][t]=1,d[++T][0]=q1,d[T][1]=w1,d[T][2]=1;
            }
        }
        z[q][w][t]=0; 
        S++;
    }
}
void ss(int q,int w,int x,int y,int e,int I)
{
    f[x][y][e][0]=min(f[x][y][e][0],f[x][y][w][0]+f[x][y][e-w][0]-a[I][x][y]);
    f[x][y][e][1]=min(f[x][y][e][1],f[x][y][w][1]+f[x][y][e-w][0]-a[I][x][y]);
    f[x][y][e][1]=min(f[x][y][e][1],f[x][y][w][0]+f[x][y][e-w][1]-a[I][x][y]);
    fo(i,q,m1)if(er[i]&e)ss(i+1,w+er[i],x,y,e,I);
}
int main()
{
    freopen("treasure.in","r",stdin);
    freopen("treasure.out","w",stdout);
    er[1]=1;fo(i,2,10)er[i]=er[i-1]<<1;
    int q,w,e;
    read(h),read(n),read(m);
    fo(i,1,h)fo(j,1,n)fo(k,1,m)read(a[i][j][k]);
    fo(I,1,h)
    {
        fo(i,1,n)fo(j,1,m)b[i][j]=0;
        read(m1);
        fo(i,1,m1)
        {
            read(q),read(w);
            b[q][w]=er[i];
        }
        memset(f,60,sizeof(f));
        FAIL=f[0][0][0][0];
        fo(i,1,n)fo(j,1,m)f[i][j][b[i][j]][1]=F[i][j]+a[I][i][j],f[i][j][b[i][j]][0]=a[I][i][j];
        fo(k,0,er[m1+1]-1)
        {
            fo(i,1,n)fo(j,1,m)ss(1,0,i,j,k,I);
            SPFA(k,I);
        }
        fo(i,1,n)fo(j,1,m)F[i][j]=f[i][j][er[m1+1]-1][1];
    }
    ans=1e9;
    fo(i,1,n)fo(j,1,m)ans=min(ans,F[i][j]);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值