CodeForces - 821D Okabe and City (spfa+思维构图)

题意:给你一个,n*m的二维平面,其中有k个格子被路灯点亮,现在Okabe要从点(1,1)走到点(n,m),他只能走被点亮的格子,而Okabe自己可以点亮一行或者一列的所有格子(在经过后会熄灭且只能在最初亮灯的地方选择点亮一行或者一列的所有格子),问他最少多少次点亮格子可以走到点(n,m)。

第一种做法:

直接根据题意进行建图,将所有被点亮的格子记录,如果两个格子相邻,则把两点间的边权设为0,如果两个格子行或列之差为1但不相邻,则把两点间的边权设为1,最后计算最短路就行了。需要注意的是,如果终点没有被点亮,那么需要设点(n+1,m+1)为终点,并加入到点亮的点集合中。

#include<bits/stdc++.h>

using namespace std;

const int maxn=1e4+10;

const int inf=0x3f3f3f3f;

int n,m,k,beg,en;

struct node{
	int x,y;
}a[maxn];

int dis[maxn];
bool vis[maxn];

void spfa(){
	memset(dis,0x3f,sizeof dis);
	queue<int> q;
	q.push(beg);
	vis[beg]=1;
	dis[beg]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=1;i<=k;i++){
			if(i==u) continue;
			int w=inf;
			int dx=abs(a[i].x-a[u].x);
			int dy=abs(a[i].y-a[u].y);
			if(dx+dy==1){
				w=0;
			}
			else if(dx<=2||dy<=2){
				w=1;
			}
			if(dis[i]>dis[u]+w){
				dis[i]=dis[u]+w;
				if(!vis[i]){
					q.push(i);
					vis[i]=1; 
				}
			}
		}
	}
	if(dis[en]!=inf) printf("%d\n",dis[en]);
	else printf("-1\n");
}

int main(){
	scanf("%d%d%d",&n,&m,&k);
	bool flag=0;
	for(int i=1;i<=k;i++){
		scanf("%d%d",&a[i].x,&a[i].y);
		if(a[i].x==n&&a[i].y==m) flag=1,en=i;
		if(a[i].x==1&&a[i].y==1) beg=i;
	}
	if(!flag){
		a[++k].x=n+1;
		a[k].y=m+1;
		en=k;
	}
	spfa();
	return 0;
}

第二种做法:

首先考虑:1,他们在同一列或者同一行, 2,他们的列或者行相邻,这时候就点亮相邻这一行/列,就可以到达那行的任意一点了3,他们列或者行坐标差值绝对值=2,也就是隔着一行,那他就点亮隔着这一行,也能到达。

我们把每两个点之间相通用的金币算出来,就建一个图,每相通的两个点连边,权值就是花费的金币,然后直接最短路就好了。。这时候,如果两个相邻,很简单,连起来权值是0就好了,如果不相邻呢,一般想法是k2暴力建边,那样假如所有点都在两行上,每一行最多5e3个点,容易tle也会mle,所以我们就要拆点了,把每行每列都规定一个入点,一个出点,每一行/列到这两个点的距离是0,连的时候把相应的入点出点连起来就好了,这样相当于那两个点连起来,还不用费空间,怎样连呢,前面说了花费是1的几种可能,总结下, 只要行或者列差值小于2,他就可以花费一个金币到达,所以我们也不用暴力任何两个点,因为我们已经把每行每列的出点入点跟这一行/列的所有点的权值都变成0了,所以,我们只需要暴力每个亮的点,把他所在行跟所在列周围的两行/两列连起来就好了。。k4的复杂度,在这之前把相邻点的边也建好,不用k2暴力,用一个map标记出现过得点,到时候暴力k个点,查找他周围四个点是否出现过,出现过就连起来就好了,k4复杂度~

#include<bits/stdc++.h>

using namespace std;

const int maxn=5e4+10;
const int inf=0x3f3f3f3f;

int dir[4][2]={0,1,0,-1,1,0,-1,0};

struct node{
	int x,y;
	node(){}
	node(int x,int y):x(x),y(y){}
}a[maxn];

vector<node> v[maxn];
map<int,int> mp[maxn];

int dis[maxn],n,m,k,beg;
bool vis[maxn];

void spfa(int u){
	dis[u]=0;
	queue<int> q;
	q.push(u);
	vis[u]=1;
	while(!q.empty()){
		u=q.front();q.pop();
		vis[u]=0;
		for(int i=0;i<v[u].size();i++){
			int to=v[u][i].x,w=v[u][i].y;
			if(dis[to]>dis[u]+w){
				dis[to]=dis[u]+w;
				if(!vis[to]){
					q.push(to);
					vis[to]=1;
				}
			}
		}
	}
	int ans=inf;
	for(int i=1;i<=k;i++){
		if(a[i].x==n&&a[i].y==m){//如果终点本来是亮着的,那就直接是到这个点的dis就好了
			ans=min(ans,dis[i]);
		}
		if(n-a[i].x<=1||m-a[i].y<=1){//如果没亮着,只能跟在他某一行/列或者相邻行/列才行,相隔一行过不去,因为终点不是亮着的
			ans=min(ans,dis[i]+1);//这两种方式取一个最小值
		} 
	}
	if(ans==inf){
		printf("-1\n");
	}else printf("%d\n",ans);
}

inline void init(){
	memset(vis,0,sizeof vis);
	memset(dis,0x3f,sizeof dis);
	for(int i=0;i<maxn;i++){
		v[i].clear();
		mp[i].clear();
	}
}

int main(){
	while(scanf("%d%d%d",&n,&m,&k)!=EOF){
		init();
		for(int i=1;i<=k;i++){
			scanf("%d%d",&a[i].x,&a[i].y);
			mp[a[i].x][a[i].y]=i;//map记录每个横纵坐标是第几个出现的,也就是第几个点
			if(a[i].x==1&&a[i].y==1) beg=i;//找出1,1点是第几个点,他肯定要亮着,否则走不到
		}
		for(int i=1;i<=k;i++){//把相邻的点之间建好边
			for(int j=0;j<4;j++){//看他周围有亮着点吗
				int tx=a[i].x+dir[j][0];
				int ty=a[i].y+dir[j][1];
				if(mp[tx][ty]){
					v[mp[tx][ty]].push_back(node(i,0));
					v[i].push_back(node(mp[tx][ty],0));
				}
			}
		}
		for(int i=1;i<=k;i++){	//建立每行每列的出点入点 
			v[i].push_back(node(a[i].x+k,0));
			v[a[i].x+k+n].push_back(node(i,0));
			v[i].push_back(node(a[i].y+n*2+k,0));
			v[a[i].y+n*2+k+m].push_back(node(i,0));
		}
		for(int i=1;i<=n;i++){//建立每个点到相邻2个以内的行列的边,通过与对应行列的入点出点建边
			for(int j=-2;j<=2;j++){
				int tmp=i+j;
				if(tmp>=1&&tmp<=n){
					v[tmp+k].push_back(node(i+k+n,1));
					v[i+k].push_back(node(tmp+k+n,1));
				}
			}
		}
		for(int i=1;i<=m;i++){
			for(int j=-2;j<=2;j++){
				int tmp=i+j;
				if(tmp>=1&&tmp<=m){
					v[tmp+2*n+k].push_back(node(i+k+2*n+m,1));
					v[i+2*n+k].push_back(node(tmp+k+2*n+m,1));
				}
			}
		}
		spfa(beg);
	}
	return 0;
}

参考博客:

https://blog.csdn.net/lyg_air/article/details/77163091

https://blog.csdn.net/qq_34374664/article/details/74602707

猜你喜欢

转载自blog.csdn.net/qq_40679299/article/details/84504867