洛谷 P3956 棋盘 解题报告
P3956 棋盘
题目描述
有一个\(m×m\)的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在要从棋盘的最左上角走到棋盘的最右下角。
任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的), 你只能向上、 下、左、 右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费1个金币。
另外, 你可以花费2个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个魔法不能连续使用, 而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法; 只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。
现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?
输入输出格式
输入格式:
第一行包含两个正整数\(m,n\) ,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。
接下来的\(n\)行,每行三个正整数\(x, y, c\), 分别表示坐标为\((x,y)\)的格子有颜色\(c\)。
其中\(c=1\)代表黄色,\(c=0\)代表红色。 相邻两个数之间用一个空格隔开。 棋盘左上角的坐标为(1,1) ,右下角的坐标为\((m,m)\) 。
棋盘上其余的格子都是无色。保证棋盘的左上角,也就是(1,1) 一定是有颜色的。
输出格式:
一个整数,表示花费的金币的最小值,如果无法到达,输出-1 。
数据规模与约定
对于 30% 的数据, 1≤m≤5,1≤n≤10 。
对于 60% 的数据, 1≤m≤20,1≤n≤200 。
对于 100% 的数据, 1≤m≤100,1≤n≤1,000 。
这道题让我收获巨大。
首先,先入为主的,我想到了DP,初始态为左上角,终态为右下角,每个状态由它的左边和上面的点转移。
因为是否用了魔法也算作状态,所以DP方程设计的比较繁琐
令\(dp[i][j][k][l]\)代表\(i\)行\(j\)列颜色为\(k(1/0)\)是否使用魔法(\(l\)为0为没有使用)
转移直接看代码就行
code:
#include <cstdio>
#include <cstring>
int min(int x,int y){return x<y?x:y;}
const int M=105;
int g[M][M],dp[M][M][2][2],m,n;
int main()
{
scanf("%d%d",&m,&n);
int x,y,c;
memset(g,-1,sizeof(g));
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&x,&y,&c);
g[x][y]=c;
}
dp[0][1][0][0]=dp[0][1][1][0]=0;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
{
int col=g[i][j];
if(col!=-1)
{
dp[i][j][col][0]=min(min(dp[i-1][j][col][0],dp[i-1][j][col][1]),dp[i][j][col][0]);
dp[i][j][col][0]=min(min(dp[i-1][j][col^1][0],dp[i-1][j][col^1][1])+1,dp[i][j][col][0]);
dp[i][j][col][0]=min(min(dp[i][j-1][col][0],dp[i][j-1][col][1]),dp[i][j][col][0]);
dp[i][j][col][0]=min(min(dp[i][j-1][col^1][0],dp[i][j-1][col^1][1])+1,dp[i][j][col][0]);
}
else
{
dp[i][j][1][1]=min(min(dp[i-1][j][1][0],dp[i-1][j][0][0]+1),min(dp[i][j-1][1][0],dp[i][j-1][0][0]+1))+2;
dp[i][j][0][1]=min(min(dp[i-1][j][0][0],dp[i-1][j][1][0]+1),min(dp[i][j-1][0][0],dp[i][j-1][1][0]+1))+2;
}
}
int ans=min(min(dp[m][m][1][1],dp[m][m][1][0]),min(dp[m][m][0][1],dp[m][m][0][0]));
if(ans==0x3f3f3f3f)
printf("-1\n");
else
printf("%d\n",ans);
return 0;
}
然而
最开始我以为是方程的某些细节错了(毕竟转移方程好麻烦啊),后来翻了洛谷上的一些题解才明白可以从右边或下面转。
那么状态转移方程似乎就不好设计了,因为不知道要先做谁啊。
翻了一下题解,大多是一些记搜或是搜索剪枝,但一个最短路建模吸引了我的注意。
于是\(Dijk\)的贪心思想出来了。
用二叉堆维护,每次取出答案最小的点去更新其他的点,则一定是正确的(图中无负边)
具体地,维护一个五元组\((x,y,col,is,w)\),分别代表坐标,颜色,是否用膜法,当前答案,每次朝四个方向更新即可。
从我的理解来看,这道题把我对 Dijk,动态规划和广搜的理解放在了一起,让我感觉到它们有着本质上的相似,只是在基础上又加上了一些限制而已。
广搜:不管是否优秀都进行暴力的枚举
动态规划:基于Dijk的贪心使方程有了无后效性
Dijk:贪心的思想其实也基于了一种动态规划
欢迎大家提出不足之处!
code:
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M=105;
const int X[5]={0,0,1,0,-1};
const int Y[5]={0,1,0,-1,0};
int g[M][M],m,n,used[M][M][2][2];
struct node
{
int x,y,col,is,w;
bool friend operator <(node n1,node n2)
{
return n1.w>n2.w;
}
node(){}
node(int x,int y,int col,int is,int w)
{
this->x=x;this->y=y;this->col=col;
this->is=is;this->w=w;
}
};
priority_queue <node > q;
int main()
{
scanf("%d%d",&m,&n);
int x0,y0,c;
memset(g,-1,sizeof(g));
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&x0,&y0,&c);
g[x0][y0]=c;
}
node t0(1,1,g[1][1],0,0);
q.push(t0);
int ans=0x3f3f3f3f;
while(!q.empty())
{
node from=q.top();
q.pop();
int x=from.x,y=from.y,col=from.col,is=from.is,w=from.w;
if(used[x][y][col][is]) continue;
if(x==m&&y==m) {ans=w;break;}
used[x][y][col][is]=1;
for(int i=1;i<=4;i++)
{
int xx=X[i]+x,yy=Y[i]+y;
if(xx==0||xx==m+1||yy==0||yy==m+1) continue;
if(g[xx][yy]!=-1)
{
if(!used[xx][yy][g[xx][yy]][0])
{
node tt(xx,yy,g[xx][yy],0,w+(col^g[xx][yy]));
q.push(tt);
}
}
else if(!is)
{
if(!used[xx][yy][col][1])
{
node tt(xx,yy,col,1,w+2);
q.push(tt);
}
}
}
}
if(ans==0x3f3f3f3f)
printf("-1\n");
else
printf("%d\n",ans);
return 0;
}
洛谷 P3956 棋盘 解题报告的更多相关文章
- 2017普及组D1T3 洛谷P3956 棋盘
2017普及组D1T3 洛谷P3956 棋盘 原题 题目描述 有一个m×m的棋盘,棋盘上每一个格子可能是红色.黄色或没有任何颜色的.你现在要从棋盘的最左上角走到棋盘的最右下角. 任何一个时刻,你所站在 ...
- 洛谷 P1979 华容道 解题报告
P1979 华容道 题目描述 小\(B\)最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时 ...
- 洛谷 P2058 海港 解题报告
P2058 海港 题目描述 小K是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客. 小K对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况: ...
- BZOJ 3545 / 洛谷 P4197 Peaks 解题报告
P4197 Peaks 题目描述 在\(\text{Bytemountains}\)有\(N\)座山峰,每座山峰有他的高度\(h_i\).有些山峰之间有双向道路相连,共\(M\)条路径,每条路径有一个 ...
- 虔诚的墓主人(BZOJ1227)(洛谷P2154)解题报告
题目描述 小W是一片新造公墓的管理人.公墓可以看成一块N×M的矩形,矩形的每个格点,要么种着一棵常青树,要么是一块还没有归属的墓地. 当地的居民都是非常虔诚的基督徒,他们愿意提前为自己找一块合适墓地. ...
- 洛谷 P3956 棋盘(BFS)
传送门:Problem P3956 https://www.cnblogs.com/violet-acmer/p/9827010.html 题解: BFS 相关变量解释: color[maxn][ma ...
- 洛谷 P3956 棋盘
题目描述 有一个m ×m的棋盘,棋盘上每一个格子可能是红色.黄色或没有任何颜色的.你现在要从棋盘的最左上角走到棋盘的最右下角. 任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的), 你只能向上 ...
- 洛谷 P3956 棋盘(记忆化搜索)
嗯... 题目链接:https://www.luogu.org/problem/P3956 这是一道比较好搜的题,注意一些剪枝.预处理和魔法的处理问题(回溯). AC代码: #include<c ...
- 洛谷 P2672 推销员 解题报告
P2672 推销员 题目描述 阿明是一名推销员,他奉命到螺丝街推销他们公司的产品.螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户.螺丝街一共有N家住户,第i家住户到入口的距离为 ...
随机推荐
- SqlServer执行大的数据库脚本出错解决方法
如果执行线上项目拷下来sqlserver的.sql的数据库脚本文件,如果文件较大时,那么就会报错内存不足之类的. 这时可以在命令提示符使用命令来执行脚本文件.切记,执行前先改一下数据库存放位置! 命令 ...
- redis系列--深入哨兵集群
一.前言 在之前的系列文章中介绍了redis的入门.持久化以及复制功能,如果不了解请移步至redis系列进行阅读,当然我也是抱着学习的知识分享,如果有什么问题欢迎指正,也欢迎大家转载.而本次将介绍哨兵 ...
- 20155327 2017-2018-2《Java程序设计》课程总结
20155327 2017-2018-2<Java程序设计>课程总结 每周作业链接汇总 预备作业1:我期望的师生关系,对课程的展望:https://www.cnblogs.com/l97- ...
- 11、可扩展MySQL+12、高可用
11.1.扩展MySQL 静态分片:根据key取hash,然后取模: 动态分片:用一个表来维护key与分片id的关系: 11.2.负载均衡 12. 12.2导致宕机得原因: 35%环境+35%性能+2 ...
- Spring Boot (十三): Spring Boot 小技巧
一些 Spring Boot 小技巧.小知识点 初始化数据 我们在做测试的时候经常需要初始化导入一些数据,如何来处理呢?会有两种选择,一种是使用 Jpa,另外一种是 Spring JDBC .两种方式 ...
- 一个可以代替冗长switch-case的消息分发小框架
在项目中,我需要维护一个应用层的字节流协议.这个协议的每条报文都是一个字节数组,数组的头两个字节表示消息的传送方向,第三.四个字节表示消息ID,也就是消息种类,再往后是消息内容.时间戳.校验码等……整 ...
- Asp.Net_from标签中的Enctype=multipart/form-data作用
ENCTYPE="multipart/form-data"用于表单里有图片上传. <form name="userInfo" method="p ...
- supervisor管理进程 superlance对进程状态报警
supervisor介绍 首先,介绍一下supervisor.Supervisor(http://supervisord.org/)是用Python开发的一个client/server服务,是Linu ...
- OD之去除nag弹窗(四)
在某些方面,一个软件如果没有注册的话,老是会弹出烦人的注册弹窗,就如下图一样: 出现了两次弹窗,开始一次,关闭后又一次,老办法,拖进OD进行分析;不过看出程序很简单,就出现了messagebox的调用 ...
- ats透明代理
透明代理是拦截客户端和服务器之间的连接而不可见的代理能力(比如ats). 必须要有一个网关设备,所有网络流量都通过该设备从客户端传递到Internet(或外部云).网关负责有效的将ATS拼接到该流量的 ...