传送门:https://www.acwing.com/problem/content/1133/
这题的建图方式相当地恶心...不过这题的思想还是很有趣的。
分析
假如没有门,朴素的bfs
就足够了,但这题有门,所以我们考虑增加一维状态,用来记录当前节点拥有的钥匙的情况。
对于当前节点(房间):
- 如果有钥匙,基于贪心的思想,把钥匙全部拿上肯定不亏,所以状态变成拿起当前节点的所有钥匙,注意到这个过程并没有走动,所以转移时候的“边权”为 (0) 。
- 如果没有钥匙,什么都干不了,不用转移。
然后可以从当前房间走到附近的房间,进行状态转移:这个时候,“边权”为 (1) 。
从上面的讨论可知,因为当前节点转移过程的边权只可能是 (0,1) ,故考虑双端队列广搜,而这个过程完全类似于dijkstra
。
代码:
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=20, M=400;
struct node{
int to,next,w;
}e[M];
int head[M],tot;
void add(int u,int v,int w){e[tot].to=v;e[tot].w=w;e[tot].next=head[u];head[u]=tot++;}
int g[N][N];
set<PII> edges;
int key[M];
int n,m,p,s;
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
void build(){
int k;
cin>>k;
while(k--){
int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2;
int c; cin>>c;
int u=g[x1][y1], v=g[x2][y2];
if(c) add(u,v,c), add(v,u,c);
edges.insert({u,v}); edges.insert({v,u});
}
cin>>s;
while(s--){
int x,y,w; cin>>x>>y>>w;
key[g[x][y]]|=1<<(w-1);
}
for(int x=1;x<=n;x++)
for(int y=1;y<=m;y++)
for(int op=0;op<4;op++){
int kx=x+dx[op], ky=y+dy[op];
if(!kx || kx>n || !ky || ky>m) continue;
int p1=g[x][y], p2=g[kx][ky];
if(!edges.count({p1,p2})) add(p1,p2,0), add(p2,p1,0);
}
}
int d[M][1<<12];
bool vis[M][1<<12];
int bfs(){
memset(d,0x3f,sizeof d);
deque<PII> q;
q.push_front({1,0});
d[1][0]=0;
while(q.size()){
auto hd=q.front(); q.pop_front();
int ver=hd.x;
if(vis[ver][hd.y]) continue;
vis[ver][hd.y]=true;
if(ver==n*m) return d[ver][hd.y];
int st=hd.y;
if(key[ver]){
st|=key[ver];
if(d[ver][st]>d[ver][hd.y]){
d[ver][st]=d[ver][hd.y];
q.push_front({ver,st});
}
}
for(int i=head[ver];~i;i=e[i].next){
int go=e[i].to;
if(e[i].w && !(st>>(e[i].w-1)&1)) // 障碍
continue;
if(d[go][st]>d[ver][hd.y]+1){
d[go][st]=d[ver][st]+1;
q.push_back({go,st});
}
}
}
return -1;
}
int main(){
memset(head,-1,sizeof head);
cin>>n>>m>>p;
for(int i=1,t=1;i<=n;i++)
for(int j=1;j<=m;j++)
g[i][j]=t++; // 记录点的编号
build(); // 建图
cout<<bfs()<<endl;
return 0;
}