任务
解法
考虑二维时的情况,
可以发现是对指定点的最小生成树,被称为斯坦纳树
注意到点数很少,
我们可以利用状态压缩Dp求解。
设
f[i][j][s]
表示,以
(i,j)
为根,连通状态为
s
的最小代价。
显然有,
注意到,
用①转移时,由于 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这样的形式之后,就会缩短一位,重新枚举。