蒜头君救人 状压DP

蒜头君救人

题目描述

蒜头君是一个乐于助人的好孩子,这天他所在的乡村发生了洪水,有多名村民被困于孤岛上,于是蒜头君决定去背他们离开困境,假设蒜头君所在的村子是 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。

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

那么为什么跑得很快呢?因为在这样的定义下, 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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值