NOIP2017普及组 棋盘
Luogu P3956 棋盘
考试的时候拿到这一题也是心情复杂,因为这种框框地图题平常也是有打的,以为会很容易,但是又想到这种题Debug很累,所以心情复杂。
但看完题我就觉得自己真天真啊……这哪是难Debug,是难打啊……但是当时并不知道怎么做,只能按之前打了一个DFS暴力搜索+一个可行性剪枝,最后成绩出来骗了50分。
考完试在Yugu群上看见kkk说这题正解是建图跑最短路,还有一些人说是记忆化搜索。因为记忆化搜索打起来方便呐,在车上就打了记忆化,但是!!这题存在后效性!!我会的记忆化是做不了这个了(弱啊我),当然好像的确是有记忆化这种做法的。
今天的解法就是跑一遍最短路。
include <cstdio>
using namespace std;
int dis[105][105],vis[105][105],map[105][105],clo[105][105];
int x[1000005],y[1000005];
const int INF=10000000;//因为权值无论如何都不会大于它,所以用它来表示无法到达(没有被更新到就是这个值)
const int SX[]={1,0,-1,0};
const int SY[]={0,-1,0,1};
int main(){
int n,m;
scanf("%d %d",&m,&n);//m*m的地图,n个点有color
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++){
dis[i][j]=INF;//表示[i][j]到[1][1]的distance
vis[i][j]=0;//标记此节点是否位于queue
map[i][j]=0;//记录此节点color
clo[i][j]=0;//记录此节点是否magic
}
//初始化
int xx,yy,s;//[xx][yy]的颜色为(s+1):因为0标记为无色,而题目的0是红色,那为了方便则+1处理
for(int i=1;i<=n;i++){
scanf("%d %d %d",&xx,&yy,&s);
map[xx][yy]=s+1;
}
dis[1][1]=0;
vis[1][1]=1;
x[1]=1;
y[1]=1;//SPFA的队列x和y分别保存横纵坐标
int head=1,tail=1,cnt=0;
while(head<=tail){
cnt=0;
for(int i=head;i<=tail;i++){//访问队列中节点
int u=x[i],v=y[i];
int nx,ny;
int book=map[u][v];//存自己的color
if(clo[u][v])book=clo[u][v];
for(int j=0;j<4;j++){//尝试四个方向
nx=u+SX[j];ny=v+SY[j];
if(!(nx>=1&&nx<=m&&ny>=1&&ny<=m))continue;
//如果越界则跳过
int w,fclo;//表示处理后需要放入队列的数据
if(book==map[nx][ny]){
w=dis[u][v];
fclo=0;
}
//如果颜色一致,则距离代价为0,不修改颜色,没有magic
else if(book!=map[nx][ny]&&map[nx][ny]!=0){
w=dis[u][v]+1;
fclo=0;
}
//如果颜色不一致但是又不需要magic,则距离+1其余不变
else if(map[nx][ny]==0&&clo[u][v]==0){
w=dis[u][v]+2;
fclo=book;
}
//如果颜色为空,但是之前无magic,则可以距离+2,颜色变成自己,magic标记clo为1
else continue;//啥都不满足就说明这节点没法到
if(w<dis[nx][ny]){//如果使得路径更优,那可以进行松弛操作
dis[nx][ny]=w;
clo[nx][ny]=fclo;
if(vis[nx][ny])continue;//如果在队列里是不需要管他了,后面会处理到的
//否则我们就需要把他入队
cnt++;//新入队数++
x[cnt+tail]=nx;
y[cnt+tail]=ny;//放在队尾
vis[nx][ny]=1;
}
}
vis[u][v]=0;
if(clo[u][v]){
map[u][v]=0;
clo[u][v]=0;
}
//如果此节点上次magic了,则需要复原
}
head=tail+1;
tail+=cnt;
}
if(dis[m][m]==INF)printf("%d\n",-1);
else printf("%d\n",dis[m][m]);
return 0;
}
我们可以把此图看成一个无向图。图上某坐标[i][j]可以认为是某节点[i][j],并且有四条路径到它的上下左右的节点,这样,我们就可以尝试着按最短路去做做了。
Dijkstra和SPFA应该都可以做,但是我个人用SPFA(比较像BFS好打)。
总结一下思想:
采用SPFA进行最短路操作,对队列中的某个节点进行松弛操作(广告:感谢浴谷教育教会我最短路的一系列玩法),若路径另一端的节点被松弛操作,我们就需要更新一下它的信息:dis(到[1][1]的距离),clo(是否用了膜法,用了膜法变成什么颜色),此节点若在队列中,则后续会操作它,若不在,则需要入队,以他为起点进行松弛操作。最后得出到点[m][m]的最短距离即为答案。