【图论】拯救大兵瑞恩

传送门: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;
}
posted @ 2021-03-14 21:43  HinanawiTenshi  阅读(65)  评论(0编辑  收藏  举报