蒜头君救人 状压DP

来源:互联网 发布:导弹升级数据 编辑:程序博客网 时间:2024/05/15 03:08

蒜头君救人

题目描述

蒜头君是一个乐于助人的好孩子,这天他所在的乡村发生了洪水,有多名村民被困于孤岛上,于是蒜头君决定去背他们离开困境,假设蒜头君所在的村子是 n×m 的网格,网格中.号代表平地,#号代表该地已被洪水淹没,A、B……等大写字母表示该地有村民被困,s代表蒜头君的起点,t代表蒜头君的终点。

蒜头君的初始速度为 k 秒一格,他每次可以向上下左右 4 个方向中的一个移动 1 格。在背上一个村民后,他的速度可能会降低,也可能会加快,但他的速度不能快于 1 秒每格,那么蒜头君想知道,他最快需要多长时间将所有村民救出?

注意:不能在终点以外的地方放下村民;可以同时背多个村民。

输入格式

第一行 3 个正整数 n,m(1n,m10),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位置(p只取初始状态下村民的位置、终点位置),那么显然有状态转移:

f[S1|(1<<y1)][S2][y]=min(dis(x,y)V[S1]+f[S1][S2][x])
(xS2,xS1,yS2,yS1)
f[S1xorT][S2|T][en]=min(f[S1xorT][S2|T][en],f[S1][S2][en])
(TS1en)

V[S]表示在背着的村民状态为S下的速度,dis(x,y)表示在原图上x,y的距离。都可以预处理出来。下面的代码采用的是floyd求距离,当然也可以用BFS。

上述两式分别表示到一个地方接村民、在终点放村民。如果不用记忆化搜索的形式,注意循环顺序。

那么为什么跑得很快呢?因为在这样的定义下,S1S2是不能有交集的,这样显然不可能的情况在循环里判断一下就可以了,实际上可能合法的情况只有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]);}