状压+分层图最短路 孤岛营救问题

让我们一起来%forever_shi神犇

题意:
有一个迷宫,一开始在左上角,要走到右下角,相邻的两个格子有些不可通过的墙,还有一些门需要有了对于的那一类钥匙才能通过。求 1 1 n n 的最短路。 n m < = 100 n*m<=100 ,门和墙总数不超过 150 150 ,迷宫中的钥匙不超过 14 14 ,同类的钥匙可能有多个。

题解:
建分层图,建图的方式是根据已有的钥匙状态建 2 k 2^k 层图,对于每层,如果有一个有钥匙并且当前层还没有这一类钥匙的位置,那么从这个位置向加上这个钥匙后的那一层的对应点连边权为 0 0 的边,对于每一层,如果相邻的没有阻挡或者已经有了钥匙就在这一层的这两个点之间连边,然后就是分层图求最短路就行了。

细节多,建议重构

#include<bits/stdc++.h>
using namespace std;
priority_queue<pair<int,int> > q;
struct node
{
    int next,to,dis;
}e[1001000];
struct nodd
{
    int x,y;
}key[2500][2500];
int h[1001000],numk[1001000];
int num,head[1001000],n,m,p,k,s,dis[1001000];
bool book[1001000];
inline void add(int from,int to,int dis)
{
    e[++num].next=head[from];
    e[num].to=to;
    e[num].dis=dis;
    head[from]=num;
}
inline void dij(int s)
{
    for(int i=1;i<=1000000;++i)
        dis[i]=2e9;
    dis[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty())
    {
        int x=q.top().second;
        q.pop();
        if(book[x])
            continue;
        book[x]=1;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(dis[v]>dis[x]+e[i].dis)
            {
                dis[v]=dis[x]+e[i].dis;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}
int cnt,numb[2500][2500],d[2500][2500];
int main()
{
    cin>>n>>m>>p>>k;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            numb[i][j]=++cnt;
    for(int i=1;i<=k;++i)
    {
        int x,y,xx,yy,g;
        scanf("%d%d%d%d%d",&x,&y,&xx,&yy,&g);
        if(g==0)
            g=-1;
        x=numb[x][y];
        y=numb[xx][yy];
        d[x][y]=g;
        d[y][x]=g;
    }
    cin>>s;
    for(int i=1;i<=s;++i)
    {
        int x,y,q;
        scanf("%d%d%d",&x,&y,&q);
        numk[q]++;//q类型钥匙的数量 
        key[q][numk[q]].x=x;//keyij表示第j个i号钥匙 
        key[q][numk[q]].y=y;
    }
    int sum=n*m;
    for(int i=0;i<(1<<p);++i)//枚举状态 
    {
        for(int j=1;j<=p;++j)//枚举钥匙的种类 
        {
            if(i&(1<<(j-1)))//如果状态i下拥有钥匙j 
                h[j]=1;
            else
                h[j]=0;
        }
        for(int j=1;j<=n;++j) 
            for(int q=1;q<=m;++q)
            {
                int x=numb[j][q];
                int y=numb[j][q+1];//同一行不同列 
                if(y&&d[x][y]!=-1)//如果不越界&xy之间不是墙 
                    if(d[x][y]==0||h[d[x][y]])//如果xy之间的钥匙种类包含于状态i或者没有门 
                    {
                        add(i*sum+x,i*sum+y,1);//同一层内加边 
                        add(i*sum+y,i*sum+x,1);
                    }
                y=numb[j+1][q];//同一列不同行
                if(y&&d[x][y]!=-1)
                    if(d[x][y]==0||h[d[x][y]])
                    {
                        add(i*sum+x,i*sum+y,1);
                        add(i*sum+y,i*sum+x,1);
                    }			 
            }
        for(int j=1;j<=p;++j)//枚举钥匙种类 
        {
            if(!h[j])//如果这种钥匙不包含于此状态 
            {
                int s1=i+(1<<(j-1));//含有这个钥匙的状态 
                for(int q=1;q<=numk[j];++q)//枚举每一个j号钥匙 
                {
                    int x=numb[key[j][q].x][key[j][q].y];//这个钥匙可以通过的两点的对应的编号 
                    add(i*sum+x,s1*sum+x,0);//从这一层的这个点向有这种钥匙的那层的点 
                }
            }
        } 		
    }	
    dij(1);
    int ans=2e9;
    for(int i=0;i<(1<<p);++i)
        ans=min(ans,dis[sum*i+cnt]);
    if(ans==2e9)
    {
        cout<<"-1";
        return 0;
    }	 
    cout<<ans;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wddwjlss/article/details/83115972