蒜头君救人
题目描述
蒜头君是一个乐于助人的好孩子,这天他所在的乡村发生了洪水,有多名村民被困于孤岛上,于是蒜头君决定去背他们离开困境,假设蒜头君所在的村子是 n×m 的网格,网格中.号代表平地,#号代表该地已被洪水淹没,A、B……等大写字母表示该地有村民被困,s代表蒜头君的起点,t代表蒜头君的终点。
蒜头君的初始速度为 k 秒一格,他每次可以向上下左右 4 个方向中的一个移动 1 格。在背上一个村民后,他的速度可能会降低,也可能会加快,但他的速度不能快于 1 秒每格,那么蒜头君想知道,他最快需要多长时间将所有村民救出?
注意:不能在终点以外的地方放下村民;可以同时背多个村民。
输入格式
第一行 3 个正整数 n,m(1≤n,m≤10),k ,分别表示村庄长度、宽度、蒜头君初始速度。
接下来 n 行,每行一个长度为
m 的字符串,表示村庄的地形,字符串意义如上所述。接下来若干行,每行一个大写字母、一个整数,表示该编号的村民会使 k 增加 / 减少多少。行数等同于地形中大写字母的个数。大写字母按字典序,即A、B、C的顺序排列,保证前后两行的字母是连续的,村民个数小于等于 10。
输出格式
输出 1 个整数,表示最小用时。
样例输入
4 4 2
s.##
..A#
.B##
…t
A -3
B 4
样例输出
17
数据规模这么小,不是搜索就是状压。
标程给出的是三进制状压DP,但是这样做使得位运算的优势不复存在了。所以时间复杂度其实也只是理性愉悦,提取某一位常数很大。而且也增加了编程复杂度。
事实上二进制状压DP是可以的,并不会达到 O(4cntcnt2) 的复杂度,而且跑得很快。
定义状态
f[S1][S2][p]
表示背着的村民状态为
S1
,已经到终点的村民为
S2
,蒜头君处在
p
位置(
V[S]
表示在背着的村民状态为
S
下的速度,
上述两式分别表示到一个地方接村民、在终点放村民。如果不用记忆化搜索的形式,注意循环顺序。
那么为什么跑得很快呢?因为在这样的定义下, S1 与 S2 是不能有交集的,这样显然不可能的情况在循环里判断一下就可以了,实际上可能合法的情况只有 3cnt 种。再加上 x,y 与两个集合间的关系,循环执行的次数不会特别多。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int N,M,K,dx[4]={0,0,1,-1},dy[4]={-1,1,0,0};
int Map[105][105],pos[15],id[15][15],tot,v[15];
int f[1234][1234][15],V[1234],U,inf;
char s[15][15];
int main()
{
int i,j,k,x,y,tx,ty,St;
char ch[3];
scanf("%d%d%d",&N,&M,&K);
for(i=1;i<=N;i++)scanf("%s",&s[i][1]);
while(scanf("%s%d",ch,&x)!=EOF)v[++v[0]]=x;
for(i=1;i<=N;i++)
for(j=1;j<=M;j++)id[i][j]=++tot;
for(i=1;i<=tot;i++)
for(j=1;j<=tot;j++)Map[i][j]=1e9;
for(i=1;i<=tot;i++)Map[i][j]=0;
for(i=1;i<=N;i++)
for(j=1;j<=M;j++)
{
if(s[i][j]=='#')continue;
x=i;y=j;
for(k=0;k<4;k++)
{
tx=x+dx[k];ty=y+dy[k];
if(tx&&ty&&tx<=N&&ty<=M&&s[tx][ty]!='#')
{
int a=id[x][y],b=id[tx][ty];
Map[a][b]=1;
}
}
}
for(k=1;k<=tot;k++)
for(i=1;i<=tot;i++)
for(j=1;j<=tot;j++)Map[i][j]=min(Map[i][j],Map[i][k]+Map[k][j]);
//floyd预处理距离
for(i=1;i<=N;i++)
for(j=1;j<=M;j++)
{
if(s[i][j]=='s')St=id[i][j];
if(s[i][j]=='t')pos[v[0]+1]=id[i][j];
if(s[i][j]>='A'&&s[i][j]<='Z')pos[s[i][j]-'A'+1]=id[i][j];
}
U=(1<<v[0])-1;
V[0]=K;
for(i=1;i<=U;i++)
{
int tmp=0;
for(j=1;j<=v[0];j++)if((i>>j-1)&1)tmp+=v[j];
V[i]=tmp+K;
if(V[i]<1)V[i]=1;
}//预处理V
memset(f,60,sizeof(f));
for(i=1;i<=v[0];i++)f[1<<i-1][0][i]=Map[St][pos[i]]*K;
f[0][0][v[0]+1]=Map[St][pos[v[0]+1]]*K;
inf=f[0][0][0];
for(j=0;j<U;j++)
for(i=0;i<=U;i++)
{
if(i&j)continue;//在这样的定义下,不能有交集
for(x=1;x<=v[0]+1;x++)
{
if(f[i][j][x]==inf||(x<=v[0]&&(!((i>>x-1)&1))))continue;
for(y=1;y<=v[0];y++)
{
if((i>>y-1)&1||(j>>y-1)&1)continue;
if(f[i|(1<<y-1)][j][y]>f[i][j][x]+V[i]*Map[pos[x]][pos[y]])f[i|(1<<y-1)][j][y]=f[i][j][x]+V[i]*Map[pos[x]][pos[y]];
}
y=v[0]+1;
if(f[i][j][y]>f[i][j][x]+V[i]*Map[pos[x]][pos[y]])f[i][j][y]=f[i][j][x]+V[i]*Map[pos[x]][pos[y]];
}
for(x=i;x;x=x-1&i)f[i^x][j|x][v[0]+1]=min(f[i^x][j|x][v[0]+1],f[i][j][v[0]+1]);//枚举子集
}
printf("%d",f[0][U][v[0]+1]);
}