5 4 ....# ...#. ..... ..... ..... 2 3 1 4 1 2 3 5 2 3 3 1 5 4 2 1
7
题目大意:给定一个n*n的格子,'.'代表可以走,'#'代表不可以走,每分钟可以往相邻的格子走,又存起点和终点已知的m条隧道,可以从任意一点开始,求经过所有隧道所用的最短时间?
大致思路:先bfs预处理出任意两点的最短路,然后就转换为TSP问题,由于必经的隧道很小,所以可以用状压DP
设dp[i][j]表示当前i中二进制中为1的位已经走过,且最后一个走过的隧道为第j条隧道
【注意】必须把表示走过的某些隧道这个状态放在最外层,把隧道放在最内层,WA在这2.5h,还是不太熟悉状压DP
只有这样才能保证当前使用的状态是最优状态,否则就限定了部分遍历隧道的顺序
①若未遍历的隧道在最外层,则限定了最后一个必定经过第m-1条隧道
②若已遍历的隧道在最外层,则限定了倒数第二个必定经过第m-1条隧道
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> using namespace std; struct Node { int r,c,t; Node(int rr=0,int cc=0,int tt=0):r(rr),c(cc),t(tt) {} }u; struct Tunnel { int sr,sc,er,ec; }tun[17]; char city[17][17]; int n,m,rr,cc,mx; int time[17][17][17][17]; bool vis[17][17]; int dp[(1<<15)+5][17]; const int dr[4]={-1,0,1,0}; const int dc[4]={0,1,0,-1}; inline bool isInside(int r,int c) { return 1<=r&&r<=n&&1<=c&&c<=n; } void bfs(int r,int c) { queue<Node> q; q.push(Node(r,c,0)); memset(vis,false,sizeof(vis)); vis[r][c]=true; while(!q.empty()) { u=q.front(); q.pop(); time[r][c][u.r][u.c]=u.t; for(int i=0;i<4;++i) { rr=u.r+dr[i]; cc=u.c+dc[i]; if(isInside(rr,cc)&&city[rr][cc]!='#'&&!vis[rr][cc]) { vis[rr][cc]=true; q.push(Node(rr,cc,u.t+1)); } } } } int main() { while(2==scanf("%d%d",&n,&m)) { mx=1<<m; for(int i=1;i<=n;++i) { scanf("%s",&city[i][1]); } memset(time,0x3f,sizeof(time)); memset(dp,0x3f,sizeof(dp)); for(int i=0;i<m;++i) { scanf("%d%d%d%d",&tun[i].sr,&tun[i].sc,&tun[i].er,&tun[i].ec); bfs(tun[i].er,tun[i].ec); dp[1<<i][i]=0; } for(int j=0;j<mx;++j) { for(int i=0;i<m;++i) { if((j&(1<<i))==0) {//如果当前状态的第i条隧道未走过 for(int k=0;k<m;++k) { if(dp[j][k]!=0x3f3f3f3f) {//如果当前状态的第k条隧道已走过 dp[j|(1<<i)][i]=min(dp[j|(1<<i)][i],dp[j][k]+time[tun[k].er][tun[k].ec][tun[i].sr][tun[i].sc]); } } } } } int ans=0x3f3f3f3f; for(int i=0;i<m;++i) { ans=min(ans,dp[mx-1][i]); } printf("%d\n",ans==0x3f3f3f3f?-1:ans); } return 0; }