题目传送门:https://www.acwing.com/problem/content/177/
【题目大意】
达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。翰翰的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障,导致无法启动。电路板的整体结构是一个R行C列的网格(R,C≤500),如下图所示。
每个格点都是电线的接点,每个格子都包含一个电子元件。电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。在旋转之后,它就可以连接另一条对角线的两个接点。电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。
【输入格式】
输入文件包含多组测试数据。
第一行包含一个整数T,表示测试数据的数目。
对于每组测试数据,第一行包含正整数R和C,表示电路板的行数和列数。
之后R行,每行C个字符,字符是"/“和”"中的一个,表示标准件的方向。
【输出格式】
对于每组测试数据,在单独的一行输出一个正整数,表示所需的缩小旋转次数。
如果无论怎样都不能使得电源和发动机之间连通,输出NO SOLUTION。
【数据范围】
1≤R,C≤500,
1≤T≤5
【输入样例】:
1
3 5
\/\
\///
/\\
【输出样例】:
1
【样例解释】
样例的输入对应于题目描述中的情况。
只需要按照下面的方式旋转标准件,就可以使得电源和发动机之间连通。
分析:
方法一:建图+堆优化dijkstra求最短路
题目中旋转短电缆是需要花费1的代价的,旋转后是另外两个点的连接,那么我们可以根据读入的短电缆方向确定格点上边权为0的无向边,那么与其方向垂直的方向的格点可建立边权为1的无向边。那么会构成一个由点集0~(R+1)(C+1)-1组成的无向图,求解0为起点,(R+1) * (C+1)-1为终点的最短路,边权为0或者1,因此可采用dijkstra求最短路,考虑到普通的dij时间复杂度为O(N^2),其中N=501501(格点数),会超时,那么考虑使用堆优化的dij,时间复杂度为O(NlogN)可以通过。
注意建立图时分析好格点与格子坐标关系
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 500+6;
int r,c;
char s[MAXN][MAXN];
struct node {
int u,v,w,next;
};
int d[MAXN*MAXN];
bool vis[MAXN*MAXN];
int cnt, head[MAXN*MAXN];
node edge[MAXN*MAXN*4]; //一个格子里会有四条边
void init(){
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
memset(s,0,sizeof(s));
cnt = 0;
}
void read(){
cin >> r >> c;
for(int i= 0; i< r; i++) scanf("%s",s[i]);
}
void addedge(int u,int v,int w){
edge[++cnt]=(node){u,v,w,head[u]};
head[u] = cnt;
}
void pre_edge(){
int xc = c+1;
for(int i = 0; i< r; i++){
for(int j = 0; j< c; j++){
if(s[i][j] == '/'){ //将(i,j)坐标的格子边上四个点对应建立无向边,构成一个图。
addedge(xc*i+j, xc*(i+1)+j+1 , 1);
addedge( xc*(i+1)+j+1 ,xc*i+j, 1);
addedge(xc * i + j +1 ,xc*(i+1)+j , 0);
addedge(xc *(i+1)+j ,xc * i + j +1, 0);
}
else{
addedge(xc*i+j, xc*(i+1)+j+1 , 0);
addedge( xc*(i+1)+j+1 ,xc*i+j, 0);
addedge(xc * i + j +1 ,xc*(i+1)+j , 1);
addedge(xc *(i+1)+j ,xc * i + j +1 , 1);
}
}
}
}
void dij(){ //堆优化的dijkstra求最短路
memset(d,0x3f,sizeof(d));
memset(vis,0,sizeof(vis));
priority_queue < pair<int,int> >q;
int ed = (c+1)*r+c;
d[0] = 0;
q.push(make_pair(0,0));
while(!q.empty()){
int u = q.top().second;
q.pop();
if(vis[u]) continue;
vis[u] = true;
for(int i = head[u];i; i = edge[i].next){
int v = edge[i].v;
int w = edge[i].w;
if(d[u] + w < d[v]){
d[v] = d[u] + w;
q.push(make_pair(-1*d[v],v));
}
}
}
}
int main(){
int t;
cin >> t;
while(t--){
init();
read();
pre_edge();
dij();
int step = d[(c+1)*r+c] ;
if(step == 0x3f3f3f3f) printf("NO SOLUTION\n");
else printf("%d\n",step);
}
return 0;
}
方法二:ZYX写的宽搜,在此记录,用双端队列,如果这条分支时边权为0,就沿着该分支到达的新节点从队头入队,如果这条分支是边权为1的边,就从普通宽搜一样从队尾入,思路详见代码中注释。
#include<bits/stdc++.h>
using namespace std;
const int Maxn=505;
const int gzx[4]={0,0,1,1},gzy[4]={0,1,1,0};
const int gz[4]={0,1,0,1},dx[4]={-1,-1,1,1},dy[4]={-1,1,1,-1};
int r,c;
int Map[Maxn][Maxn];//每一个格子的电缆 0:\ 1:/
int minstep[Maxn][Maxn];
bool vis[Maxn][Maxn];
//起点坐标为(0,0) 终点坐标为(r,c)
struct node{
int x,y,cnt;
};
bool check(int x,int y){
if(x<0||y<0||x>r||y>c)return false;
return true;
}
void work(){
deque<node> q;
memset(vis,0,sizeof(vis));
memset(minstep,0x3f,sizeof(minstep));
q.push_front((node){0,0,0});
while(!q.empty()){
node temp=q.front();
q.pop_front();
int x=temp.x,y=temp.y,cnt=temp.cnt;
if(vis[x][y])continue;
vis[x][y]=true;
if(x==r && y==c){printf("%d\n",cnt);return;}
for(int i=0;i<4;i++){
int gx=x+gzx[i],gy=y+gzy[i];
if(gx>0 && gy>0 && gx<=r && gy<=c){
int nx=x+dx[i],ny=y+dy[i];
if(check(nx,ny)){
if(minstep[nx][ny]==0x3f3f3f3f){
if(Map[gx][gy]!=gz[i]){q.push_back((node){nx,ny,cnt+1});minstep[nx][ny]=cnt+1;}
else{q.push_front((node){nx,ny,cnt});minstep[nx][ny]=cnt;}
}
else if(Map[gx][gy]==gz[i] && cnt<minstep[nx][ny]){
q.push_front((node){nx,ny,cnt});
}
}
}
}
}
printf("NO SOLUTION\n");
}
int main(){
//可以理解成从起点走向不同的格点 中途可以更改道路,最后走到终点
//那么过程中就要记录当前更改道路的次数,和宽搜一样,保证了到达一个格点时,一定是用了最少的更改道路
//然后还要注意一个细节:从一个点向其他点行走时不一定要更改道路,也就是说更改道路次数不一定都是递增的,所以要用双端队列维护队列单调性
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&r,&c);
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++){
char c=getchar();
while(c!='\\'&&c!='/')c=getchar();
if(c=='\\')Map[i][j]=0;
else Map[i][j]=1;
//将字符转化为数字
}
}
work();
}
return 0;
}