[CERC2017] Donut Drone 题解
给一种不用倍增的不带 \(log\) 的做法。
约定 \(\mathrm{up}_i,\mathrm{down}_i,\mathrm{left}_i,\mathrm{right}_i\) 表示 \(x/y\) 坐标在 \(i\) 位置时向上 / 下 / 左 / 右方向循环移动一步的位置。
首先考虑暴力,即从起点直接模拟走 \(k\) 步到达目标点,时间复杂度 \(\mathcal O\left(\sum k\right)\),而 \(k\) 是 \(10^9\) 量级,显然不行。
简单观察可以发现,在图不改变的情况下从图中任意一点出发的情形是相同的,而图大小只有 \(r\times c\),这说明走至多 \(r\times c\) 步一定会走到一个已经经过的点,则会形成一个循环节,而循环节长度也至多是 \(r\times c\) 的。
那么问题转化成快速走完 \(r\times c\) 步,考虑在不带修改的情况下怎么做,发现时间复杂度可接受的上限是 \(\mathcal O\left(m(r+c)\right)\),这启示我们可以运用分块的思想,将 \(r\times c\) 步分成若干个 \(c\) 步和若干个 \(1\) 步,即分解成 \(pc+q\) 的形式,然后每次跳 \(c\) 步,一个粗劣的想法是预处理一个 \(\mathrm{skip}_{i,j}\) 表示从位置 \((i,j)\) 走 \(c\) 步能走到哪里,这样我们可以直接用倍增或别的什么方法预处理 \(\mathrm{skip}\) 数组和循环节,先不论时间复杂度,特别是带上修改以后,实现就非常困难。
考虑为什么实现这么困难,究其根本是需要维护的东西太多了,我们希望能通过询问时的一些常数开销减少维护量,然后我们发现我们可以只维护第一列的信息,这就相对好维护多了,先预处理出 \(\mathrm{step}_{i,j}\) 表示 \((i,j)\) 走 \(1\) 步能走到 \((\mathrm{step}_{i,j},\mathrm{right}_j)\) ,\(\mathrm{skip}_i\) 表示 \((i,1)\) 跳 \(c\) 步后会回到 \((\mathrm{skip}_i,1)\),循环节可以在询问时找,具体每次询问时先走到第一列,在第一列跳直到找到循环节或者遇到了剩余步数不到 \(c\),然后对循环节取模,然后再跳 \(c\) 步最多跳 \(r\) 次,再走最多 \(c\) 步,那么我们就将单次询问的时间复杂度压在了 \(\mathcal O\left(r+c\right)\)。
我们发现我们的询问时间复杂度已经非常优美了,实现难度也不大,我们可以接下来考虑让修改去迎合询问的操作,分别考虑一次修改对 \(\mathrm{step}\) 和 \(\mathrm{skip}\) 的影响,先考虑较为简单的 \(\mathrm{step}\),答案发现修改 \((x,y)\) 只会对 \((\mathrm{up}_x,\mathrm{left}_y),(x,\mathrm{left}_y),(\mathrm{down}_x,\mathrm{left}_y)\),三个位置产生影响,将这三个位置重新做就好了,那么再考虑如果 \(\mathrm{step}_{x,y}\) 的修改会对 \(\mathrm{skip}\) 产生的影响,即将会走到 \((x,y)\) 的每一行的 \(\mathrm{skip}\) 都修改为 \((x,y)\) 走到的那个 \((p,1)\) 的 \(p\)。
想要求出 \(p\) 是很简单的,直接走到 \(p\) 的位置就好了,但是想要处理会被影响的 \(\mathrm{skip}\) 还是有些难度的,但我们可以发现一个性质,即被影响的 \(\mathrm{skip}\) 一定构成一个区间,证明也非常简单,我们发现如果有一组 \([l,r]\) 满足 \(\mathrm{skip}_l=p,\mathrm{skip}_r=p\),但存在一个 \(t\) 满足 \(t\in(l,r),\mathrm{skip}\ne p\),则 \(t\) 想走向另一个终点所需要经过的路径一定和 \(l\) 或 \(r\) 走向 \(p\) 的路径有交或交叉,这两种情况都是不可能的,证明可以自行画图手模一下。
这样的话就可以很方便地求出答案了,从 \(x\) 慢慢反向推到 \(1\),做到 \(k\) 时,维护一组 \([l,r]\) 表示从 \(\forall i\in[l,r],\ (i,k)\to(p,1)\),我们可以一步一步往前推,具体实现可以看代码,时间复杂度是 \(\mathcal O\left(c\right)\) 的。
这样的话加上预处理的时间复杂度总时间复杂度就是 \(\mathcal O\left(rc+m(r+c)\right)\),比倍增法少一个 \(log\)。
c++ 代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
template<typename Ty>
Reimu clear(Ty &x) { Ty().swap(x); }
const int N = 2010;
int n, m, px, py;
int l[N], r[N], u[N], d[N], skip[N], vis[N];
int a[N][N], step[N][N];
Reimu update(int i, int j) {
int t = r[j], t1 = u[i], t2 = d[i];
if (a[i][t] > a[t1][t] && a[i][t] > a[t2][t]) step[i][j] = i;
else step[i][j] = a[t1][t] > a[t2][t] ? t1 : t2;
}
Marisa calc(int x, int y) {
while (y ^ 1) x = step[x][y], y = r[y];
return x;
}
Reimu move(int c) {
memset(vis + 1, 0, n << 2);
while (c && py ^ 1) px = step[px][py], py = r[py], --c;
int cur = 1;
while (c >= m && !vis[px]) vis[px] = cur++, px = skip[px], c -= m;
if (vis[px]) c %= m * (cur - vis[px]);
while (c >= m) px = skip[px], c -= m;
while (c--) px = step[px][py], py = r[py];
cout << px << ' ' << py << '\n';
}
Reimu solve(int x, int y) {
int p = calc(step[x][y], r[y]), L = x, R = x, len = 1;
for (int i = y; --i && len > 0 && len < n; ) {
if (step[u[L]][i] == L) L = u[L], ++len;
else if (step[L][i] ^ L && step[L][i] ^ d[L]) L = d[L], --len;
if (step[d[R]][i] == R) R = d[R], ++len;
else if (step[R][i] ^ R && step[R][i] ^ u[R]) R = u[R], --len;
}
if (len <= 0) return;
if (len >= n) L = d[R];
if (L <= R) for (int i = L; i <= R; ++i) skip[i] = p;
else {
for (int i = 1; i <= R; ++i) skip[i] = p;
for (int i = L; i <= n; ++i) skip[i] = p;
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> m;
iota(u + 1, u + n + 1, 0); iota(d + 1, d + n + 1, 2);
iota(l + 1, l + m + 1, 0); iota(r + 1, r + m + 1, 2);
d[u[1] = n] = r[l[1] = m] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
update(i, j);
}
}
for (int i = 1; i <= n; ++i) skip[i] = calc(step[i][1], 2);
px = py = 1;
int T; cin >> T; while (T--) {
string s; int x; cin >> s >> x;
if (s[0] ^ 'c') move(x);
else {
int y, v; cin >> y >> v;
a[x][y] = v;
for (int t: {u[x], x, d[x]}) update(t, l[y]);
for (int t: {u[x], x, d[x]}) solve(t, l[y]);
}
}
return 0;
}
原文地址:https://www.cnblogs.com/LaoMang-no-blog/p/16573702.html
- Spring集成RabbitMQ-使用RabbitMQ更方便
- Nodejs学习笔记(三)--- 模块
- 使用JClouds在Java中获取和发布云服务器
- Silverlight单元测试框架
- Enterprise Library深入解析与灵活应用(2): 通过SqlDependency实现Cache和Database的同步
- 让你感觉不真实的13个伟大科学成就和发现
- 分析Silverlight跨域调用
- Spring集成RabbiMQ-Spring AMQP新特性
- Nodejs学习笔记(二)--- 事件模块
- 巧用FireFox来调试Silverlight
- Nodejs学习笔记(一)--- 简介及安装Node.js开发环境
- WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
- 区块链技术(一):Truffle开发入门
- Nodejs学习笔记(一)——初识Nodejs
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- redis+php实现微博(一)注册与登录功能详解
- PHP序列化的四种实现办法与横向对比
- php设计模式之观察者模式定义与用法经典示例
- Laravel获取所有的数据库表及结构的方法
- redis+php实现微博(二)发布与关注功能详解
- PHP实现小程序批量通知推送
- Thinkphp5.0 框架Model模型简单用法分析
- php设计模式之单例模式用法经典示例分析
- PHP实现统计代码行数小工具
- redis+php实现微博(三)微博列表功能详解
- php设计模式之工厂模式用法经典实例分析
- laravel 关联关系遍历数组的例子
- PHP开启目录引索+fancyindex漂亮目录浏览带搜索功能
- 解决Laravel 使用insert插入数据,字段created_at为0000的问题
- 关于php unset对json_encode的影响详解