【洛谷4011】孤岛营救问题(状压SPFA)

版权声明:本文为作者原创,若转载请注明地址。 https://blog.csdn.net/chenxiaoran666/article/details/82054418

点此看题面
大致题意:有一个 N M 的四联通迷宫,相邻两个可能互通,可能有一扇门,也可能有一堵墙。对于第 i 类的门,你需要有第 i 类的钥匙才可以通过。问你从 ( 1 , 1 ) 到达 ( N , M ) 的最短路。


第一步:建图

看到种类数 10 ,应该不难想到状压吧!而且,我们还可以给每个格子一个编号(计算方法: p o s ( x , y ) = ( x 1 ) m + y )。
我们可以用 k e y i 来表示编号为 i 的格子上有的钥匙状压之后的结果,并用 d i s i , j 来表示从 ( 1 , 1 ) 出发,到达编号为 i 的节点时拥有的钥匙种类状压之后的结果为 j 的最短路径。
接下来,我们可以枚举每一个节点 i 与其一个相邻的节点 j ,然后分类讨论:

  • 如果它们互通:在它们之间建一条边权为1的边。
  • 如果它们之间有一扇门:假设这扇门的种类为 x ,就在他们之间建一条边权为 2 x 的边。
  • 如果它们之间有一堵墙:不建边。

其中,边权就相当于走这条边的条件,一定要当前拥有的钥匙含有开启这扇门的钥匙才行。


第二步:跑最(wang)短(luo)路(liu)

建完了图,就可以开始跑最短路了,当然,这题作为网络流24题之一,你一定要写网络流也没人拦你
一开始初始化拥有的钥匙状压后的结果为1(不然就无法过边权为1的边),然后,每到达一个节点,就可以更新拥有的钥匙后的状压结果了。
最短路的过程应该还是比较简单的,这题难就难在建图。


代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define N 10
#define pos(x,y) ((x-1)*m+y)
#define add(x,y,z) (e[++ee].to=y,e[ee].nxt=lnk[x],e[lnk[x]=ee].val=z)
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
int n,m,s,ans=1e9,ee=0,Inqueue[N*N+5][1<<N+1],dis[N*N+5][1<<N+1],lnk[N*N+5],keys[N*N+5],f[N*N+5][N*N+5];
struct edge
{
    int to,nxt,val;
}e[((N*N)<<2)+5];
struct Status
{
    int x,v;
}q[((N*N)<<N+1)+5];
inline void read(int &x)
{
    x=0;int f=1;static char ch;
    while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
    x*=f;
}
inline void write(int x)
{
    if(x<0) pc('-'),x=-x;
    if(x>9) write(x/10);
    pc(x%10+'0');
}
inline void SPFA()//跑最短路
{
    register int i,j,H=Inqueue[1][1]=1,T=1,maxx=1<<s+1,MOD=(N*N)<<N+1;register Status k;q[1]=(Status){1,1};
    for(i=1;i<=n*m;++i) for(j=0;j<maxx;++j) dis[i][j]=1e9;dis[1][1]=0;
    while((H%=MOD)^(T+1))//只要队列没有空
    {
        k=q[H++],Inqueue[k.x][k.v]=0,dis[k.x][k.v|keys[k.x]]=min(dis[k.x][k.v|keys[k.x]],dis[k.x][k.v]),k.v|=keys[k.x];//更新当前拥有的钥匙的数量
        for(i=lnk[k.x];i;i=e[i].nxt)//枚举当前节点能到达的每一个节点
        {
            if((k.v&e[i].val)^e[i].val) continue;//如果不满足条件,就跳过
            if(dis[k.x][k.v]+1<dis[e[i].to][k.v])//如果更短就更新最短距离 
            {
                dis[e[i].to][k.v]=dis[k.x][k.v]+1;
                if(!Inqueue[e[i].to][k.v]) Inqueue[e[i].to][k.v]=1,q[(++T)%=MOD]=(Status){e[i].to,k.v};//如果不在队列中,就将其加入队列
            }
        }
    }
    for(i=0;i<maxx;++i) ans=min(ans,dis[n*m][i]);//求出最优的答案
}
int main()
{
    register int i,j,t,x,y,z;
    for(read(n),read(m),read(s),i=1;i<=n*m;++i) for(j=1;j<=n*m;++j) f[i][j]=-1;
    for(read(t);t;--t)
    {
        static int u,v;
        read(x),read(y),u=pos(x,y),read(x),read(y),v=pos(x,y),read(x),f[u][v]=f[v][u]=x;
        if(x) add(u,v,1<<f[u][v]),add(v,u,1<<f[u][v]);//如果这不是一堵墙,就新建一条边
    }
    for(i=1;i<=n;++i)
    {
        for(j=1;j<=m;++j)
        {
            //对于互通的情况建边
            if(i>1&&!(~f[pos(i,j)][pos(i-1,j)])) add(pos(i,j),pos(i-1,j),1);
            if(i<n&&!(~f[pos(i,j)][pos(i+1,j)])) add(pos(i,j),pos(i+1,j),1);
            if(j>1&&!(~f[pos(i,j)][pos(i,j-1)])) add(pos(i,j),pos(i,j-1),1);
            if(j<m&&!(~f[pos(i,j)][pos(i,j+1)])) add(pos(i,j),pos(i,j+1),1);
        }
    }
    for(read(t);t;--t) read(x),read(y),read(z),keys[pos(x,y)]|=1<<z;//读入每一把钥匙,更新每一个位置的钥匙种类状压后的值
    return SPFA(),write(ans^1000000000?ans:-1),fwrite(pp,1,pp_,stdout),0;
}

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/82054418
今日推荐