[洛谷P4772]灰化肥,会挥发
题目
Description
在 Farmer Justin 的农场中有许多灰化肥,它们都堆积在A仓库里。为了方便施肥,Farmer Justin 需要修一些公路使得他能用拖拉机把这些灰化肥拉到其他仓库里。由于 Farmer Justin 及其懒惰,所以他只想一次拉完所有的灰化肥送到其他仓库里。但是灰化肥见光易挥发,所以 Farmer Justin 需要尽快把这些灰化肥拉完。现在告诉你Farmer Justin农场的构成地图,请你帮帮他计划一条从 A 仓库出发走完所有仓库的方案吧!由于Farmer Justin 非常的讨厌浪费时间,所以你只需要告诉他最短的距离和走过所有农场的顺序。(注意:拖拉机走的时候是四联通的。)
Input
第一行三个正整数 R,C,N分别表示地图大小和仓库数量。 下面给出一个 R 行 C 列的地图,其中 .
表示空地,可以修建公路;*
表示是 Farmer Justin 的农业区,不可以修建公路;用大写字母表示仓库编号。
Output
第一行一个正整数表示最短的距离。
第二行表示拖拉机走过仓库的方案(由仓库编号组成的字符串)。若有多种方案,输出字典序最小的方案。
数据保证有解。
Sample Input
5 5 3 A.**C *.... B*... .**.. .....
Sample Output
16 ACB
思路
因为要经过所有仓库,所以自然而然地想到用状压$dp$来处理最短距离和经过的仓库;
只需先用$BFS$预处理每个仓库之间的最短距离即可;
状态转移方程就为;
$dp[i][k]=min(dp[i][k],dp[j][k \oplus (1<<(i-1))]+a[j][i]);$
$s[i][k]=s[j][k \oplus (1<<(i-1))]+char(i+'A'-1);$
$k$表示经过的仓库集合,$i$表示当前经过的仓库;
题目要求经过字典序最小,用$if$判断一下即可;
代码
#include<bits/stdc++.h> typedef long long ll; using namespace std; const ll _=501; ll n,m,t; char c[_][_]; ll d[_][_],f[_][_]; ll dx[5]={0,0,1,0,-1}; ll dy[5]={0,1,0,-1,0}; ll a[17][17],dp[17][1<<16]; string s[17][1<<16]; struct node { ll x,y; }; queue<node> q; bool check(ll x,ll y) { if(x>0&&x<=n&&y>0&&y<=m) return 1; return 0; } void bfs(ll x,ll y,char s) { memset(f,0,sizeof(f)); memset(d,0,sizeof(d)); node num,nxt; num.x=x; num.y=y; f[x][y]=1; q.push(num); while(!q.empty()) { num=q.front(); q.pop(); for(ll i=1;i<=4;i++) { nxt.x=num.x+dx[i]; nxt.y=num.y+dy[i]; if(check(nxt.x,nxt.y)&&!f[nxt.x][nxt.y]&&c[nxt.x][nxt.y]!='*') { d[nxt.x][nxt.y]=d[num.x][num.y]+1; f[nxt.x][nxt.y]=1; if(c[nxt.x][nxt.y]>='A'&&c[nxt.x][nxt.y]<='Z') {//如果nxt点是仓库,则记录两仓库的距离 a[s-'A'+1][c[nxt.x][nxt.y]-'A'+1]=d[nxt.x][nxt.y]; a[c[nxt.x][nxt.y]-'A'+1][s-'A'+1]=d[nxt.x][nxt.y]; } q.push(nxt); } } } } int main() { memset(dp,127,sizeof(dp)); scanf("%lld%lld%lld",&n,&m,&t); for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) cin>>c[i][j]; for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) if(c[i][j]>='A'&&c[i][j]<='Z') bfs(i,j,c[i][j]);//以每个仓库为起点,计算其他仓库到这个仓库的距离 dp[1][1]=0;//从第一个仓库出发 for(ll k=1;k<=(1<<t)-1;k+=2) for(ll i=1;i<=t;i++) for(ll j=1;j<=t;j++) if(k&(1<<(i-1))&&k&(1<<(j-1))) { if(dp[i][k]==dp[j][k^(1<<(i-1))]+a[j][i])//如果距离相等,则记录字典序最小的路径 s[i][k]=min(s[i][k],s[j][k^(1<<(i-1))]+char(i+'A'-1)); if(dp[i][k]>dp[j][k^(1<<(i-1))]+a[j][i]) { dp[i][k]=dp[j][k^(1<<(i-1))]+a[j][i];//状态转移 s[i][k]=s[j][k^(1<<(i-1))]+char(i+'A'-1); } } ll ans=INT_MAX; string ss; for(ll i=2;i<=t;i++) { if(ans==dp[i][(1<<t)-1])//距离相等,找字典序最小的路径 ss=min(ss,s[i][(1<<t)-1]); if(ans>dp[i][(1<<t)-1]) { ans=dp[i][(1<<t)-1]; ss=s[i][(1<<t)-1]; } } printf("%lld\n",ans); cout<<"A"<<ss; return 0; }