b_vj_Hunter(dij预处理宝藏与宝藏外界的最短距离+状压dp)
有一个n*m的网格,正数格子代表被研究的费用,-1代表不能被研究,且网格中有k个宝藏;问一个宝藏猎人以网格外部为起点进入网格,并收集所有宝藏回到起点的最小花费。(1<=K<=13,n,m<=200)
思路:f[i][j]表示终点在宝藏i,且宝藏状态为j时的最少花费;
由于要回到起点,所以在dijkstra中用out[id]表示从i走到边界的最短距离
#include<iostream>
#include<math.h>
#include<queue>
using namespace std;
const int N=205, M=20, inf=0x3f3f3f3f, dir[4][2] = { {1,0},{0,-1},{0,1},{-1,0} };
int n,m,K,d[N][N],sp[N][N],g[N][N],out[N],f[M][1<<M];
struct node {
int x,y,w;
}T[N];
struct cmp{
bool operator()(node& a, node& b){
return a.w > b.w;
}
};
void dij(int sx, int sy, int id) {
for (int i=0; i<n; i++) for (int j=0; j<m; j++) sp[i][j]=inf;
priority_queue<node, vector<node>, cmp> q;
q.push({sx,sy,0}), sp[sx][sy]=0;
while (!q.empty()) {
int x=q.top().x, y=q.top().y; q.pop();
if (x==0 || x==n-1 || y==0 || y==m-1) //宝藏到边界的距离,注g[sx][sy]的花费没有尚未算进去
out[id]=min(out[id], sp[x][y]);
for (int k=0; k<4; k++) {
int tx=x+dir[k][0], ty=y+dir[k][1];
if (tx>=0 && tx<n && ty>=0 && ty<m && g[tx][ty]!=-1 && sp[tx][ty]>sp[x][y]+g[tx][ty]) {
sp[tx][ty]=sp[x][y]+g[tx][ty];
q.push({tx,ty,sp[tx][ty]});
}
}
}
}
int main() {
std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int q; cin>>q;
while (q--) {
cin>>n>>m;
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
cin>>g[i][j];
cin>>K;
for (int i=0; i<K; i++) cin>>T[i].x>>T[i].y;
//求出宝藏与宝藏之间的最短距离(注:dij只能求单源最短路)
for (int i=0; i<K; i++) {
out[i]=inf; //out[i]就是宝藏到外界的最短距离
dij(T[i].x,T[i].y,i);
for (int j=0; j<K; j++) {
d[i][j]=sp[T[j].x][T[j].y];
}
}
int tot=1<<K, ans=inf;
for (int i=0; i<K; i++) for (int j=0; j<tot; j++) f[i][j]=inf;
for (int i=0; i<K; i++) f[i][1<<i]=g[T[i].x][T[i].y]+out[i]; //外界到宝藏i的距离
for (int j=0; j<tot; j++)
for (int s=0; s<K; s++) if (j&(1<<s) && f[s][j]!=inf) {
for (int e=0; e<K; e++) if ((j&(1<<e))==0)
f[e][j|(1<<e)]=min(f[e][j|(1<<e)], f[s][j]+d[s][e]);
}
for (int i=0; i<K; i++) ans=min(ans, f[i][tot-1]+out[i]); //终点在i,且从i出去
cout<<ans<<'\n';
}
return 0;
}