题目:

洛谷3638

分析:

卡了一天的神题……(OrzJumpmelon)

首先预处理出从点\(p\)向\(d\)方向出发最终能到达的点\(nxt[p][d]\)。这个可以直接记忆化搜索解决。如果出现环说明不能向这个方向出发,设为\(-1\)。

  1. struct point
  2. {
  3. int x, y;
  4. point(const int _x = 0, const int _y = 0)
  5. : x(_x), y(_y) {}
  6. };
  7. inline bool check(const point &p)
  8. {
  9. return p.x >= 0 && p.x < h && p.y >= 0 && p.y < w;
  10. }
  11. inline int ptoi(const point &p)
  12. {
  13. return p.x * w + p.y;
  14. }
  15. bool vis[P][DIR], insta[P][DIR];
  16. int dfs(const point &u, const int &d)
  17. {
  18. int id = ptoi(u);
  19. if (vis[id][d])
  20. return nxt[id][d];
  21. if (insta[id][d])
  22. {
  23. vis[id][d] = true;
  24. return nxt[id][d] = -1;
  25. }
  26. int dd = d;
  27. if (map[id] == LEFT)
  28. dd = (dd + 1) & 3;
  29. if (map[id] == RIGHT)
  30. dd = (dd + 3) & 3;
  31. point v = point(u.x + dx[dd], u.y + dy[dd]);
  32. if (!check(v) || map[ptoi(v)] == WALL)
  33. {
  34. vis[id][d] = true;
  35. return nxt[id][d] = id;
  36. }
  37. else
  38. {
  39. insta[id][d] = true;
  40. nxt[id][d] = dfs(v, dd);
  41. insta[id][d] = false;
  42. vis[id][d] = true;
  43. return nxt[id][d];
  44. }
  45. }

然后考虑用\(dp[i][j][u]\)表示令编号为\([i,j]\)的复合机器人在\(u\)点的最少步数,最终答案就是\(min(dp[0][n-1][u])\)。有两种转移方式:

1.把\([i,k]\)和\((k,j]\)两个机器人在\(p\)点拼起来,即:

\[dp[i][j][u]=min(dp[i][k][u]+dp[k+1][j][u])
\]

2.把\([i,j]\)机器人推到\(u\)点,即(其中\(v\)能一步走到\(u\)即存在满足\(nxt[v][d]=u\)的\(d\)):

\[dp[i][j][u]=dp[i][j][v]+1
\]

第一种直接区间DP即可。第二种存在循环更新,但是长得很像最短路……

于是我码了Dijkstra,卡常卡到死也没卡过去,还借此机会跟Jumpmelon谝了一天qwq。

下面介绍一下Jumpmelon给我讲的优化:开两个队列,第一个队列是一开始的点,按照\(dis\)排好序(\(dis[u]\)表示\(dp[i][j][u]\),下同);第二个队列是已经更新,等待用来更新别的点的队列,初始为空。每次将两个队列队首中\(dis\)较小的一个取出来(相当于Dijkstra的堆顶)来松弛其他点,这样复杂度是\(O(n+m)\)的,成功去掉堆的\(\log m\)。为什么这样是对的呢?

注意到所有边权都是\(1\),于是点\(v\)被更新时第二个队列的队尾(如果存在)的\(dis\)一定不会大于\(dis[v]\),所以直接把\(v\)插到队尾不会影响第二个队列的有序性。原因如下。

考虑反证。假设之前第二个队列是有序的,在某一次更新后变得无序了。设用于更新的点是\(u\),被更新的点是\(v\),更新前第二个队列的队尾为\(t\),满足\(dis[v]=dis[u]+1\),\(dis[t]>dis[v]\)。由于边权都是\(1\),那么曾用于更新\(t\)的点\(p\)满足\(dis[p]=dis[t]-1\geq dis[v]>dis[u]\)。既然\(dis[u]<dis[p]\),由于之前两个队列都是有序的,且每次是取出两队列队首的较小值更新,那么\(u\)显然应该在\(p\)之前被取出,矛盾。所以这种情况不存在。既然能保持两个队列的有序性,那么就保证了每次取出\(dis\)最小的点,也就不需要堆了。(没上文化课,表达能力为\(0\),自己都觉得佶屈聱牙跟个T嘴z子z一样。感性理解就好)。

代码:

  1. #include <cstdio>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <cctype>
  5. #include <queue>
  6. #include <functional>
  7. #include <vector>
  8. #undef i
  9. #undef j
  10. #undef k
  11. #undef true
  12. #undef false
  13. #undef min
  14. #undef max
  15. #undef sort
  16. #undef swap
  17. #undef if
  18. #undef while
  19. #undef for
  20. #undef printf
  21. #undef scanf
  22. #undef putchar
  23. #undef getchar
  24. #define _ 0
  25. using namespace std;
  26. namespace zyt
  27. {
  28. template<typename T>
  29. inline bool read(T &x)
  30. {
  31. char c;
  32. bool f = false;
  33. x = 0;
  34. do
  35. c = getchar();
  36. while (c != EOF && c != '-' && !isdigit(c));
  37. if (c == EOF)
  38. return false;
  39. if (c == '-')
  40. f = true, c = getchar();
  41. do
  42. x = x * 10 + c - '0', c = getchar();
  43. while (isdigit(c));
  44. if (f)
  45. x = -x;
  46. return true;
  47. }
  48. inline bool read(char &c)
  49. {
  50. do
  51. c = getchar();
  52. while (c != EOF && !isgraph(c));
  53. return c != EOF;
  54. }
  55. template<typename T>
  56. inline void write(T x)
  57. {
  58. static char buf[20];
  59. char *pos = buf;
  60. if (x < 0)
  61. putchar('-'), x = -x;
  62. do
  63. *pos++ = x % 10 + '0';
  64. while (x /= 10);
  65. while (pos > buf)
  66. putchar(*--pos);
  67. }
  68. const int W = 5e2, P = W * W, N = 9, DIR = 4, INF = 0x3f3f3f3f, B = 18;
  69. const int U = 0, L = 1, D = 2, R = 3, WALL = N, LEFT = N + 1, RIGHT = N + 2, BLANK = N + 3;
  70. const int dx[DIR] = {-1, 0, 1, 0};
  71. const int dy[DIR] = {0, -1, 0, 1};
  72. int n, w, h, p, nxt[P][DIR], map[P], dp[N][N][P];
  73. struct point
  74. {
  75. int x, y;
  76. point(const int _x = 0, const int _y = 0)
  77. : x(_x), y(_y) {}
  78. };
  79. inline bool check(const point &p)
  80. {
  81. return p.x >= 0 && p.x < h && p.y >= 0 && p.y < w;
  82. }
  83. inline int ptoi(const point &p)
  84. {
  85. return p.x * w + p.y;
  86. }
  87. bool vis[P][DIR], insta[P][DIR];
  88. int dfs(const point &u, const int &d)
  89. {
  90. int id = ptoi(u);
  91. if (vis[id][d])
  92. return nxt[id][d];
  93. if (insta[id][d])
  94. {
  95. vis[id][d] = true;
  96. return nxt[id][d] = -1;
  97. }
  98. int dd = d;
  99. if (map[id] == LEFT)
  100. dd = (dd + 1) & 3;
  101. if (map[id] == RIGHT)
  102. dd = (dd + 3) & 3;
  103. point v = point(u.x + dx[dd], u.y + dy[dd]);
  104. if (!check(v) || map[ptoi(v)] == WALL)
  105. {
  106. vis[id][d] = true;
  107. return nxt[id][d] = id;
  108. }
  109. else
  110. {
  111. insta[id][d] = true;
  112. nxt[id][d] = dfs(v, dd);
  113. insta[id][d] = false;
  114. vis[id][d] = true;
  115. return nxt[id][d];
  116. }
  117. }
  118. void Shortest_Path(const int l, const int r)
  119. {
  120. typedef pair<int, int> pii;
  121. static pii tmp[P];
  122. static bool vis[P];
  123. static queue<int> q1, q2;
  124. while (!q1.empty())
  125. q1.pop();
  126. while (!q2.empty())
  127. q2.pop();
  128. memset(vis, 0, sizeof(bool[p]));
  129. int *dis = dp[l][r], cnt = 0;
  130. for (int i = 0; i < p; i++)
  131. if (map[i] != WALL && dis[i] < INF)
  132. tmp[cnt++] = make_pair(dis[i], i), vis[i] = true;
  133. sort(tmp, tmp + cnt);
  134. for (int i = 0; i < cnt; i++)
  135. q1.push(tmp[i].second);
  136. while (!q1.empty() || !q2.empty())
  137. {
  138. int u = ((q1.empty() || (!q2.empty() && dis[q1.front()] > dis[q2.front()])) ? q2.front() : q1.front());
  139. if (!q1.empty() && u == q1.front())
  140. q1.pop();
  141. else
  142. q2.pop();
  143. vis[u] = false;
  144. for (int i = 0; i < DIR; i++)
  145. {
  146. int v = nxt[u][i];
  147. if (~v && dis[v] > dis[u] + 1)
  148. {
  149. dis[v] = dis[u] + 1;
  150. if (!vis[v])
  151. q2.push(v), vis[v] = true;
  152. }
  153. }
  154. }
  155. }
  156. int work()
  157. {
  158. read(n), read(w), read(h);
  159. p = w * h;
  160. for (int i = 0; i < n; i++)
  161. for (int j = i; j < n; j++)
  162. memset(dp[i][j], INF, sizeof(int[p]));
  163. for (int i = 0; i < p; i++)
  164. {
  165. char c;
  166. read(c);
  167. if (isdigit(c))
  168. {
  169. map[i] = c - '1';
  170. dp[map[i]][map[i]][i] = 0;
  171. }
  172. else if (c == '.')
  173. map[i] = BLANK;
  174. else if (c == 'x')
  175. map[i] = WALL;
  176. else if (c == 'A')
  177. map[i] = LEFT;
  178. else
  179. map[i] = RIGHT;
  180. }
  181. for (int i = 0; i < h; i++)
  182. for (int j = 0; j < w; j++)
  183. {
  184. int id = ptoi(point(i, j));
  185. if (map[id] != WALL)
  186. for (int d = 0; d < DIR; d++)
  187. nxt[id][d] = dfs(point(i, j), d);
  188. }
  189. for (int l = 1; l <= n; l++)
  190. for (int i = 0; i + l - 1 < n; i++)
  191. {
  192. int j = i + l - 1;
  193. for (int u = 0; u < p; u++)
  194. if (map[u] != WALL)
  195. for (int k = i; k < j; k++)
  196. dp[i][j][u] = min(dp[i][j][u], dp[i][k][u] + dp[k + 1][j][u]);
  197. Shortest_Path(i, j);
  198. }
  199. int ans = INF;
  200. for (int i = 0; i < p; i++)
  201. ans = min(ans, dp[0][n - 1][i]);
  202. if (ans == INF)
  203. write(-1);
  204. else
  205. write(ans);
  206. return (0^_^0);
  207. }
  208. }
  209. int main()
  210. {
  211. return zyt::work();
  212. }

【BZOJ3205_洛谷3638】[APIO2013]机器人(动态规划)的更多相关文章

  1. 洛谷 P4012 深海机器人问题【费用流】

    题目链接:https://www.luogu.org/problemnew/show/P4012 洛谷 P4012 深海机器人问题 输入输出样例 输入样例#1: 1 1 2 2 1 2 3 4 5 6 ...

  2. 洛谷 P5469 - [NOI2019] 机器人(区间 dp+拉格朗日插值)

    洛谷题面传送门 神仙题,放在 D1T2 可能略难了一点( 首先显然对于 P 型机器人而言,将它放在 \(i\) 之后它会走到左边第一个严格 \(>a_i\) 的位置,对于 Q 型机器人而言,将它 ...

  3. 洛谷P1280 && caioj 1085 动态规划入门(非常规DP9:尼克的任务)

    这道题我一直按照往常的思路想 f[i]为前i个任务的最大空暇时间 然后想不出来怎么做-- 后来看了题解 发现这里设的状态是时间,不是任务 自己思维还是太局限了,题做得太少. 很多网上题解都反着做,那么 ...

  4. 【洛谷P1126】机器人搬重物

    题目大意:给定一个 N 行,M 列的地图,一个直径为 1.6 的圆形机器人需要从起点走到终点,每秒钟可以实现:向左转,向右转,向前 1-3 步.求从起点到终点最少要多长时间. 题解:相比于普通的走迷宫 ...

  5. 洛谷P4012 深海机器人问题(费用流)

    传送门 图给的好坑……还得倒过来…… 用大佬的图做个示范 我们考虑左图吧 把每一个点向下连边,容量$1$,费用为给出的价值(表示一个机器人可以过去取得标本) 再连一条边,容量$inf$,费用$0$(表 ...

  6. 洛谷P4012 深海机器人问题(费用流)

    题目描述 深海资源考察探险队的潜艇将到达深海的海底进行科学考察. 潜艇内有多个深海机器人.潜艇到达深海海底后,深海机器人将离开潜艇向预定目标移动. 深海机器人在移动中还必须沿途采集海底生物标本.沿途生 ...

  7. Solution -「NOI 2021」「洛谷 P7740」机器人游戏

    \(\mathcal{Description}\)   Link.   自己去读题面叭~ \(\mathcal{Solution}\)   首先,参悟[样例解释 #2].一种暴力的思路即为钦定集合 \ ...

  8. 洛谷P3639 [APIO2013] 道路费用 [生成树的特殊算法]

    题目传送门 道路费用 格式难调,题面就不放了. 分析: 这是一道要细(yan)心(jing)的生成树的好(gui)题. 首先我们看到$k$的范围非常小,那么我们就可以直接$2^k$枚举每一条加边是否选 ...

  9. 洛谷 P1464 Function【动态规划(递推)/记忆化搜索(递归)】

    题目描述 对于一个递归函数w(a,b,c) 如果a<=0 or b<=0 or c<=0就返回值1. 如果a>20 or b>20 or c>20就返回w(20,2 ...

随机推荐

  1. Django DTL模板语法中的循环

    from django.shortcuts import render def index(request): context={ 'books':[ '5年高考3年模拟', '家猪养殖与配种', ' ...

  2. 洛谷 1328 生活大爆炸版石头剪刀布(NOIp2014提高组)

    [题解] 简单粗暴的模拟题. #include<cstdio> #include<algorithm> #include<cstring> #define LL l ...

  3. java 使用OpenOffice文件实现预览

    1.安装OpenOffice软件 安装教程:https://jingyan.baidu.com/article/c275f6ba12c07ce33d756732.html 2.安装完成后,创建项目,p ...

  4. gdb个人使用记录

    参考博客:https://blog.csdn.net/zdy0_2004/article/details/80102076 安装gdb,查看版本确认成功: sudo apt install gdb g ...

  5. mybatis源码阅读-执行一个sql的流程(九)

    图解 图片来源:https://my.oschina.net/zudajun/blog/670373 Mapper接口调用原理 我们整合成Spring  直接使用Mapper就能执行对应的sql 表现 ...

  6. [luoguP1076] 寻宝(模拟)

    传送门 模拟就好! 然后需要把一圈的有楼梯的都记录一下,取膜乱搞. 代码 #include <cstdio> #include <iostream> #define N 100 ...

  7. Prime Land(poj 1365)

    题意:这题题意难懂,看了题解才知道的.比如第二组sample,就是5^1*2^1=10, 求10-1即9的质因数分解,从大到小输出,即3^2.本来很简单的嘿,直接最快速幂+暴力最裸的就行了. #inc ...

  8. android调试

    要进行调试,首先构建app的时候必须选择是Debug模式,而不能是Release模式. 接下来的内容转载自: http://www.cnblogs.com/gaoteng/p/5711314.html ...

  9. 关于Git的简单使用

    新电脑git push一直出问题,到现在也没有解决,但是一些git的命令还是有用的,就先记下来吧.(下图就是没解决的报错) 一.上传本地项目到git 1.初始化git git init 2.配置用户名 ...

  10. Ubuntu 16.04切换/home中文目录为英文目录

    其实这个方法是具有技巧性的,不建议使用. 1.先转换成英文 sudo gedit /etc/default/locale 将内容改为: LANG=”en_US.UTF-8″ LANGUAGE=”en_ ...