BZOJ 2143 飞飞侠(线段树优化建边 / 并查集优化最短路)【BZOJ修复工程】
整理的算法模板合集: ACM模板
点我看算法全家桶系列!!!
实际上是一个全新的精炼模板整合计划
题目链接
https://hydro.ac/d/bzoj/p/2143
是 hydro 的 BZOJ 修复工程 !(我也去领了一点题慢慢修着玩,这题就是我修的嘿嘿嘿)
题目描述
飞飞国是一个传说中的国度,国家的居民叫做飞飞侠。飞飞国是一个 N×MN\times MN×M 的矩形方阵,每个格子代表一个街区。然而飞飞国是没有交通工具的。飞飞侠完全靠地面的弹射装置来移动。每个街区都装有弹射装置。使用弹射装置是需要支付一定费用的。而且每个弹射装置都有自己的弹射能力。我们设第 iii 行第 jjj 列的弹射装置有 AijA_{ij}Aij 的费用和 BijB_{ij}Bij 的弹射能力。并规定有相邻边的格子间距离是 111 。那么,任何飞飞侠都只需要在 (i,j)(i,j)(i,j) 支付 AijA_{ij}Aij 的费用就可以任意选择弹到距离不超过 BijB_{ij}Bij 的位置了。如下图
(从红色街区交费以后可以跳到周围的任意蓝色街区。)
现在的问题很简单。有三个飞飞侠,分别叫做 XXX,YYY,ZZZ。现在它们决定聚在一起玩,于是想往其中一人的位置集合。告诉你 333 个飞飞侠的坐标,求往哪里集合大家需要花的费用总和最低。
输入格式
输入的第一行包含两个整数 NNN 和 MMM ,分别表示行数和列数。接下来是 222 个 N×MN\times MN×M 的自然数矩阵,为 AijA_{ij}Aij 和 BijB_{ij}Bij 最后一行六个数,分别代表 XXX,YYY,ZZZ 所在地的行号和列号。
输出格式
第一行输出一个字符 XXX、YYY 或者 ZZZ,表示最优集合地点。
第二行输出一个整数,表示最小费用。
如果无法集合,只输出一行 NO
输入样例
4 4
0 0 0 0
1 2 2 0
0 2 2 1
0 0 0 0
5 5 5 5
5 5 5 5
5 5 5 5
5 5 5 5
2 1 3 4 2 2
输出样例
Z
15
数据规模与约定
对于 100%100\%100% 的数据, 1≤N,M≤1501\le N, M \le 1501≤N,M≤150,0≤Aij≤1090 \le A_{ij} \le 10^90≤Aij≤109,0≤Bij≤10000\le B_{ij} \le 10000≤Bij≤1000。
Solution
本题看起来就是一道最短路模板题,我们只需要连边之后,跑三次 Dijkstra,然后 DP 计算最小总距离即可。
但是本题 n≤150n\le150n≤150,给定的是一个 N×MN\times MN×M 的矩阵,假设 m=nm=nm=n,也就是一共有 n2n^2n2 级别的点的数量,每个点连边的方式比较特殊,它会与四周距离小于 BijB_{ij}Bij 的点均有费用为 AijA_{ij}Aij 的边,每个点会连 n2n^2n2 规模的边,直接暴力连边时间复杂度为 O(n4)O(n^4)O(n4) ,时间勉强卡过,空间无法承受。
考虑优化。
优化一
发现本题的模型是一个点向一个区间连边。显然可以使用线段树优化建边,我们直接对每一行建一个线段树,行内优化连边 O(n)→O(logn)O(n)\rightarrow O(\log n)O(n)→O(logn),连边时间复杂度和空间复杂度为 O(n3logn)O(n^3\log n)O(n3logn),跑 Dijkstra 时间复杂度 O(n3lognlog(n3logn))O(n^3\log n\log (n^3\log n))O(n3lognlog(n3logn)),可以通过本题。
Code
代码详见:https://www.luogu.com.cn/blog/wucstdio/solution-p4473
优化二
考虑本题时间上可以勉强承受,空间上承受不能,所以我们考虑能否优化空间。对于每一个点,我们发现它能到达的点是可以直接枚举计算出来的,所以我们可以不进行建图,在 Dijkstra 转移的时候直接枚举能到达的点,这样就免去了建图,空间足够了,暴力转移,时间复杂度 O(n4)O(n^4)O(n4),可以勉强通过本题。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 150 + 7, INF = 0x3f3f3f3f;int n, m, s, t, ans;
int A[maxn][maxn], B[maxn][maxn];
int X[maxn], Y[maxn];
ll path[maxn][maxn];
ll dist[maxn][maxn];struct node
{int x, y;ll d;bool operator < (const node &t) const {return d > t.d;}
};void Dijkstra(int s)
{priority_queue<node> q;memset(dist, 0x3f, sizeof dist);dist[X[s]][Y[s]] = 0;q.push((node){X[s], Y[s], 0ll});while(q.size()) {node u = q.top();q.pop();if(dist[u.x][u.y] != u.d) continue;int len = B[u.x][u.y];int cost = A[u.x][u.y];for (int i = max(1, u.x - len); i <= min(n, u.x + len); ++ i) {int updown = len - abs(u.x - i);for (int j = max(1, u.y - updown); j <= min(m, u.y + updown); ++ j) {if(dist[i][j] > dist[u.x][u.y] + cost) {dist[i][j] = dist[u.x][u.y] + cost;q.push((node){i, j, dist[i][j]});}}}}for (int i = 1; i <= 3; ++ i)path[s][i] = dist[X[i]][Y[i]];
}
char name[10] = "1XYZ";int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j)scanf("%d", &B[i][j]);for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j)scanf("%d", &A[i][j]);for (int i = 1; i <= 3; ++ i)scanf("%d%d", &X[i], &Y[i]);for (int i = 1; i <= 3; ++ i)Dijkstra(i);ll ans = INF;int id = -1;for (int i = 1; i <= 3; ++ i) {ll sum = 0;for (int j = 1; j <= 3; ++ j)sum += path[j][i];if(ans > sum)ans = sum, id = i;}if(ans == INF)return 0 * puts("NO");cout << name[id] <<endl;cout << ans << endl;return 0;
}
最慢一个点 840ms/6.14MB
优化三
优化二的思路太过暴力,考虑能否优化时间复杂度。
我们从本题图的特殊性质出发分析。
发现本题是一个特殊的图,每个点能走到的点在一个范围区间之内,显然从一个起点出发跑最短路,走到一个点之后,再往下走,如果最后又回到这个点,回头了以后,显然此时到这个点的路径长度一定比之前到达这个点时更新的最短路更大,也就是说这种图有一个特殊的性质,即一个点没必要走两次,第二次走到的时候也不可能更优,就没必要再回头了,所以如果我们可以让我们走的时候不会回头,就会达到一个非常优秀的优化效果,我们把已经更新的点放进一个并查集内,根节点指向最右边节点的 下一个点 。这样当我们第二次进入这个节点时,可以 O(1)O(1)O(1) 地跳过当前所有节点。路径压缩之后,下次如果又回到了这个点,我们就可以直接 O(1)O(1)O(1) 跳过所有已经走过的点。
在插入堆的时候,我们需要比较 dist[i]+w[i]dist[i]+w[i]dist[i]+w[i] 而不是 dis[i]dis[i]dis[i] ,这样可以保证一个点被更新后不会再一次被更新。
并查集优化之后时间复杂度来到了神奇的 O(n2logn)O(n^2\log n)O(n2logn)。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 150 + 7, INF = 0x3f3f3f3f;int n, m, s, t, ans;
int A[maxn][maxn], B[maxn][maxn];
int X[maxn], Y[maxn];
ll path[maxn][maxn];
ll dist[maxn][maxn];
int fa[maxn][maxn];
bool vis[maxn][maxn];struct node
{int x, y;ll d;bool operator < (const node &t) const {return d > t.d;}
};inline int Find(int *fa, int x)
{return fa[x] ? fa[x] = Find(fa, fa[x]) : x;
}void Dijkstra(int s)
{priority_queue<node> q;memset(dist, 0x3f, sizeof dist);memset(vis, 0, sizeof vis);memset(fa, 0, sizeof fa);dist[X[s]][Y[s]] = 0;q.push((node){X[s], Y[s], A[X[s]][Y[s]]});fa[X[s]][Y[s]] = Y[s] + 1;while(q.size()) {node u = q.top();q.pop();if(vis[u.x][u.y]) continue;vis[u.x][u.y] = 1;int len = B[u.x][u.y];int cost = A[u.x][u.y];for (int i = max(1, u.x - len); i <= min(n, u.x + len); ++ i) {int updown = len - abs(u.x - i);int up = max(1, u.y - updown);int down = min(m, u.y + updown);for (int j = Find(fa[i], up); j <= down; j = Find(fa[i], j)) {if(dist[i][j] > dist[u.x][u.y] + cost) {dist[i][j] = dist[u.x][u.y] + cost;q.push((node){i, j, dist[i][j] + A[i][j]});}fa[i][j] = j + 1;}}}for (int i = 1; i <= 3; ++ i)path[s][i] = dist[X[i]][Y[i]];
}
char name[10] = "1XYZ";int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j)scanf("%d", &B[i][j]);for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j)scanf("%d", &A[i][j]);for (int i = 1; i <= 3; ++ i)scanf("%d%d", &X[i], &Y[i]);for (int i = 1; i <= 3; ++ i)Dijkstra(i);ll ans = INF;int id = -1;for (int i = 1; i <= 3; ++ i) {ll sum = 0;for (int j = 1; j <= 3; ++ j)sum += path[j][i];if(ans > sum)ans = sum, id = i;}if(ans == INF)return 0 * puts("NO");cout << name[id] <<endl;cout << ans << endl;return 0;
}
时间来到了 44ms/1.84MB
BZOJ 2143 飞飞侠(线段树优化建边 / 并查集优化最短路)【BZOJ修复工程】相关推荐
- 【CF813F】Bipartite Checking(线段树分治+可删除并查集)
文章目录 title solution code title You are given an undirected graph consisting of n vertices. Initially ...
- 线段树分治 ---- F. Extending Set of Points(线段树分治 + 可撤销并查集)
题目链接 题目大意: 你有个点集合SSS,每次往集合里面加点或者删点(如果要加的点出现过),如果(x1,y1),(x2,y1),(x1,y2),(x2,y2)(x1,y1),(x2,y1),(x1,y ...
- BZOJ4399 魔法少女LJJ【线段树合并】【并查集】
Description 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了 LJJ感叹道"这里真是个迷人的绿色世界,空气清新.淡雅 ...
- [WC2005]双面棋盘,洛谷P4121,线段树分治+可撤销并查集
正题 这题主要是来练手的,因为没写过可撤销的并查集,大概就是把每一个格子看成一个点,然后格子直接的边有很多的出现区间,把这些出现区间和对应的颜色打到线段树上,然后用可撤销的并查集来维护就可以了. #i ...
- 【割边缩点】解题报告:POJ - 3694 - Network(Tarjan割边缩点 + LCA + 并查集优化)
POJ - 3694 - Network 给定一张N个点M条边的无向连通图,然后执行Q次操作,每次向图中添加一条边,并且询问当前无向图中"桥"的数量.N≤105,M≤2∗105,Q ...
- POJ - 3694 Network(边双缩点+LCA+并查集优化)
题目链接:点击查看 题目大意:给出一个由n个点组成的无向图,现在有m次操作,每次操作都会向图中增加一条无向边,每次操作后询问当前图中有多少个桥 题目分析:题意很好理解,思路也很好想,就是代码量有点小多 ...
- 数据结构之树的应用:并查集
树的应用:并查集 并查集的概念: 三种基本操作: 例: 代码实现: 并查集的概念: 将所有的数据元素放在一个集合中,将集合分成若干个互不相交的子集,每一个子集对应一颗树,所有的自己组成森林. 三种基本 ...
- BZOJ.3938.Robot(李超线段树)
BZOJ UOJ 以时间\(t\)为横坐标,位置\(p\)为纵坐标建坐标系,那每个机器人就是一条\(0\sim INF\)的折线. 用李超线段树维护最大最小值.对于折线分成若干条线段依次插入即可. 最 ...
- BZOJ.1558.[JSOI2009]等差数列(线段树 差分)
BZOJ 洛谷 首先可以把原序列\(A_i\)转化成差分序列\(B_i\)去做. 这样对于区间加一个等差数列\((l,r,a_0,d)\),就可以转化为\(B_{l-1}\)+=\(a_0\),\(B ...
最新文章
- 【JavaSE】 单向链表的实现与讲解
- Android开发之实现每隔一段时间触发定时器android定时器
- Centos7 开启网卡配置IP并连接xshell——转
- Qt 设置当前窗口出现在左右窗口的最前面
- html中属性的作用,html的标签中 unselectable=on 属性的作用
- 计算机网络,IP地址概念及IP地址详细分类介绍、及子网掩码详细介绍MAC地址介绍、网络位,主机位、网络地址、广播地址。
- Java Spring连接Tibco Queue 总结
- LeetCode(595)——大的国家(MySQL)
- python title函数意义_Python 字符串首字母大写-Python设置字符串首字母大写-python title()作用-python title函数-嗨客网...
- [Vue warn]: Invalid prop: custom validator check failed for prop xxx.
- 【优化求解】基于matlab改进的遗传算法求解带约束的优化问题【含Matlab源码 1773期】
- Java方法重载解析
- 学术会议html模板,学术会议的常用模板
- 人机工程学产品设计案例_【设计案例】一组电子产品设计的合辑
- 2021年江阴各高中高考成绩查询,江阴高考,全市12所高中高考成绩比较
- php expecting,php – 解析错误:语法错误,意外的’.’,expecting’,’或’;’
- vivado2020报错:error when launching …vivao.bat…launcher time out“
- 移动商务进入战国时代 08年市场规模达306.5亿
- SpringBoot实现文件上传和下载
- 实现office365和visio2016共存