狀壓+分層圖最短路 孤島營救問題
阿新 • • 發佈:2018-11-08
讓我們一起來%forever_shi神犇
題意:
有一個迷宮,一開始在左上角,要走到右下角,相鄰的兩個格子有些不可通過的牆,還有一些門需要有了對於的那一類鑰匙才能通過。求
到
的最短路。
,門和牆總數不超過
,迷宮中的鑰匙不超過
,同類的鑰匙可能有多個。
題解:
建分層圖,建圖的方式是根據已有的鑰匙狀態建
層圖,對於每層,如果有一個有鑰匙並且當前層還沒有這一類鑰匙的位置,那麼從這個位置向加上這個鑰匙後的那一層的對應點連邊權為
的邊,對於每一層,如果相鄰的沒有阻擋或者已經有了鑰匙就在這一層的這兩個點之間連邊,然後就是分層圖求最短路就行了。
細節多,建議重構
#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;
}