状压dp+bfs--bzoj1556墓地秘密

传送门

状压好题0.0
其实难点不是状压吧···应该是前面的预处理
要用到类似于spfa的bfs
RE了无数回结果发现变量名定义重了mdzz


首先说一下dp的状态定义,因为发现从i到j停下来多少次是取决于上一次撞到i的方向的

所以首先想到了dp[i][j][k]表示以k方向撞到i这个机关后已经撞过的机关为j集合的最小步数

但是我们可以发现这只和所有机关以及各个机关四周的四个格子有关

所以我们可以去掉方向维,变成dp[i][j]表示最后撞了j的撞过的是i集合的最小步数


接下来我们要考虑预处理出两个机关之间的步数
同状态定义的过程相似,我们可以只留下4*t个点
dis[i][j][k]表示从i到j,k方向的步数
最后去更新fd数组,fd[i][j]表示i到j的最小步数,这时候我们成功去掉方向维

预处理把每个机关的四周的格子当做起点做一次bfs
算机关四周的格子就是(u-1)*4+v+1,u表示机关编号,v表示方向


奉上re了好久终于AC的代码:

#include<iostream> 
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define maxn 65
#define N 105
#define inf 100000000
#define id(u,v) (u-1)*4+v+1
using namespace std;
int n,m,t,nx,ny,a[N][N],fd[maxn][maxn],dis[N][N][5],dp[1<<16][maxn];
int dir[2][4]={{0,0,-1,1},{1,-1,0,0}},bin[20],qx[200005],qy[200005];
char s[N];
bool vis[N][N];

inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
} 
struct qwq {int x,y;}st[20];

void bfs(int x,int y,int id){
    if(a[x][y] || x<1 || y<1 || x>n || y>m) return;
    memset(dis,0x3f,sizeof dis);
    qx[0]=x,qy[0]=y; vis[x][y]=1;
    for(int k=0;k<4;k++) dis[x][y][k]=0;
    int head=0,tail=1;//以后再也不偷懒用h和t了··· 
    while(head!=tail){
        int xx=qx[head],yy=qy[head];head++;
        for(int k=0;k<4;k++){
            int nowx=xx+dir[0][k],nowy=yy+dir[1][k];
            if(a[nowx][nowy] || nowx<1 || nowy<1 || nowx>n || nowy>m) continue;
            for(int i=0;i<4;i++)
                if(dis[xx][yy][k]+(k!=i)<dis[nowx][nowy][i]){
                    dis[nowx][nowy][i]=dis[xx][yy][k]+(k!=i);
                    if(!vis[nowx][nowy]){
                        vis[nowx][nowy]=1;
                        qx[tail]=nowx,qy[tail]=nowy; tail++;
                    }
                }
        }
        vis[xx][yy]=0;
    }
    for(int i=1;i<=t;i++)
        for(int k=0;k<4;k++){
            int tmp=inf,tx=st[i].x+dir[0][k],ty=st[i].y+dir[1][k];
            for(int j=0;j<4;j++)
                tmp=min(tmp,dis[tx][ty][j]+(tx+dir[0][j]!=st[i].x||ty+dir[1][k]!=st[i].y));
                fd[id][id(i,k)]=tmp;
        }
}

void DP(){
    for(int i=0;i<bin[t];i++)
        for(int j=1;j<=4*t+1;j++)
            dp[i][j]=inf;
    dp[0][4*t+1]=0;
    for(int i=0;i<bin[t];i++)
        for(int j=1;j<=4*t+1;j++)
            if(dp[i][j]!=inf)
                for(int k=1;k<=4*t;k++){
                    int x=i|bin[(k-1)/4];
                    dp[x][k]=min(dp[x][k],dp[i][j]+fd[j][k]+1);
                }
    int ans=inf;
    for(int i=1;i<=4*t;i++)
        ans=min(ans,dp[bin[t]-1][i]);
    printf("%d\n",ans);
}

int main(){
    n=rd(); m=rd(); t=rd();
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
            if(s[j]=='#') a[i][j]=-1;
    }
    for(int i=1;i<=t;i++){
        st[i].x=rd(); st[i].y=rd();
    }
    for(int i=1;i<=t;i++)
        for(int k=0;k<4;k++)
            bfs(st[i].x+dir[0][k],st[i].y+dir[1][k],id(i,k));
    nx=rd(); ny=rd();
    bfs(nx,ny,4*t+1);
    bin[0]=1;
    for(int i=1;i<=t;i++) bin[i]=bin[i-1]<<1;
    DP();
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值