题目:
http://acm.hdu.edu.cn/showproblem.php?pid=4568
题意:
n*m的棋盘中放着一些宝藏,每个点都有经过的耗时,要求从任意位置进入棋盘后拿到所有宝藏然后离开棋盘,每个点可以经过多次,求最小耗时,-1的点是不可走的。
思路:
图的大小是200*200但是宝藏最多只有13个,所以很容易想到重新建图,把13各点互相之间的距离求出来建图然后有一个虚拟点就是棋盘外,求出所有点到棋盘外的耗时。然后对这张完全图进行TSP就行了。TSP的部分简单,就是dp状压。主要是建图比较麻烦,要进行13次bfs,也就是spfa队列优化的松弛求最短路。鉴于这图展开的话m<2*n且n<40000保证是稀疏图所以用spfa基本能够保证O(km)的复杂度,每次松弛然后处理新的图f[i][j],最后TSP即可。
代码:
#define N 212
#define M 15
int n,m,k;
int flag,sum,ave,ans,res,ans1,ans2;
int a[N],b[N];
int g[N][N],f[M][M],dist[N][N],len[M];
int dir[4][2]={1,0,-1,0,0,1,0,-1};
int dp[M][1<<14];
bool vis[N][N];
void spfa(int u)
{
int x,y,w,i;
memset(dist,0x3f,sizeof(dist));
memset(vis,false,sizeof(vis));
queue<pair<int ,int> > q;
while(!q.empty())q.pop();
q.push(make_pair(a[u],b[u]));
dist[a[u]][b[u]] = 0;
while(!q.empty())
{
x = q.front().first; y = q.front().second; q.pop();
vis[x][y] = false;
if(x==0||x==(n-1)||y==0||y==(m-1))
len[u] = min(len[u],dist[x][y]);
for(i=0;i<4;i++)
{
int xx=x+dir[i][0],yy=y+dir[i][1];
if(xx>=0&&xx<n&&yy>=0&&yy<m&&g[xx][yy]!=-1&&dist[xx][yy]>dist[x][y]+g[xx][yy])
{
dist[xx][yy]=dist[x][y]+g[xx][yy];
if(!vis[xx][yy])
{
vis[xx][yy]=true;
q.push(make_pair(xx,yy));
}
}
}
}
}
int main()
{
int i,j,T,cas,t,x,y,z;
scanf("%d",&T);
cas=0;
while(T--)
{
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
for(j=0;j<m;j++)
scanf("%d",&g[i][j]);
scanf("%d",&k);
for(i=0;i<k;i++)
scanf("%d%d",&a[i],&b[i]);
memset(f,0x3f,sizeof(f));
for(i=0;i<k;i++)f[i][i]=0;
memset(len,0x3f,sizeof(len));
memset(dp,0x3f,sizeof(dp));
for(i=0;i<k;i++)
{
spfa(i);
for(j=0;j<k;j++)
f[i][j] = min(f[i][j],dist[a[j]][b[j]]);
dp[i][1<<i] = len[i]+g[a[i]][b[i]];
}
for(j=0;j<(1<<k);j++)
for(i=0;i<k;i++)
if(j&(1<<i)&&dp[i][j]!=INF)
for(x=0;x<k;x++)
if(!(j&(1<<x)))
dp[x][j|(1<<x)]=min(dp[x][j|(1<<x)],dp[i][j]+f[i][x]);
res=INF;
for(i=0;i<k;i++)
res=min(res,dp[i][(1<<k)-1]+len[i]);
printf("%d\n",res);
}
return 0;
}