题目链接:HDU4568
【题意】给一个n*m的格子,格子中有一些数,如果是正整数则为到此格子的花费,如果为-1表示此格子不可到,现在给k个宝藏的地点(k<=13),求一个人从边界外一点进入整个棋盘,然后拿走所有能拿走的宝藏的最小花费,如果一次不能拿走所有能拿到的或者根本拿不到任何宝藏,输出0.
【分析】好久没写DP了,一下子想不起来了…首先肯定需要算出每个宝藏的最短距离,然后把起点作为一个超级源点,还得加一个终点来计算,一开始WA竟然把起点终点都用一个点,然后就是标准TSP问题了;
【AC CODE】218ms
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <map>
//#include <unordered_map>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f, MAXN = 200*200+10, MAXM = 200*200*4+200*4+10;
const int f[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct Edge{
int next,to, cost;
Edge(int a = 0, int b = 0, int c = 0){
next = a, to = b, cost = c;
}
}edge[MAXM];
int tol,head[MAXN], mp[210][210],m, dis[MAXN], p[14],cost[14][14], dp[1<<14][14];
/*dp[i][j]表示状态i,当前在点j上时的最短路*/
bool inq[MAXN];
void add_edge(int u, int v, int cost)
{
edge[tol] = Edge(head[u],v,cost);
head[u] = tol++;
}
inline int id(int x, int y){
return x*m+y;
}
void spfa(int s)
{
queue<int> q;
clc(inq,0);
clc(dis,0x3f);
q.push(s);
inq[s] = true;
dis[s] = 0;
while(!q.empty())
{
int u = q.front();q.pop();
inq[u] = false;
for(int i = head[u]; ~i; i = edge[i].next)
{
int v = edge[i].to, cost = edge[i].cost;
if(INF != dis[u] && dis[v] > dis[u]+cost)
{
dis[v] = dis[u]+cost;
if(!inq[v])
{
q.push(v);
inq[v] = true;
}
}
}
}
}
int main()
{
#ifdef SHY
freopen("e:\\1.txt", "r", stdin);
#endif
int t;
scanf("%d%*c", &t);
while(t--)
{
int n;
scanf("%d %d%*c", &n, &m);
rep(i,0,n)
{
rep(j,0,m)
scanf("%d%*c", &mp[i][j]);
}
/*建图*/
int st = n*m, ed = n*m+1;
tol = 0;
clc(head,-1);
rep(i,0,n)
{
rep(j,0,m)
{
if(mp[i][j] < 0) continue;
rep(k,0,4)
{
int ni = i+f[k][0], nj = j+f[k][1];
if(0 <= ni && n > ni && 0 <= nj && m > nj && mp[ni][nj] >= 0)
add_edge(id(i,j),id(ni,nj),mp[ni][nj]);
}
if(0 == i || n-1 == i || 0 == j || m-1 == j)
{
add_edge(st,id(i,j),mp[i][j]);
add_edge(id(i,j),ed,0);
}
}
}
/*找起点以及各个宝物的最短路*/
int np,x,y;
bool ok = true;
scanf("%d%*c", &np);
p[np] = st,p[np+1] = ed;
rep(i,0,np)
{
scanf("%d %d%*c", &x, &y);
p[i] = id(x,y);
}
repe(i,0,np)
{
spfa(p[i]);
repe(j,0,np+1)
{
if(i == j || p[j] == st) continue;
cost[i][j] = dis[p[j]];
if(INF == cost[i][j])
{
ok = false;
break;
}
}
if(!ok) break;
}
if(!ok)
{
puts("0");
continue;
}
/*TSP*/
int mxn = (1<<np)-1;
clc(dp,0x3f);
clc(dp[0],0);
rep(i,0,np) dp[1<<i][i] = cost[np][i];
repe(i,1,mxn)
{
rep(j,0,np)//当前在点j
{
if(0 == (1<<j)&i || dp[i][j] != INF) continue;
rep(k,0,np)//来自上一个点k
{
if((1<<k)&i && j != k)
dp[i][j] = min(dp[i][j], dp[i^(1<<j)][k]+cost[k][j]);
}
}
}
int ans = INF;
rep(i,0,np) ans = min(ans,dp[mxn][i]+cost[i][np+1]);
if(INF == ans) puts("0");
else printf("%d\n", ans);
}
return 0;
}