Typesetting math: 100%

【JZOJ6278】【NOIP提高组A】跳房子

题目大意

在一个nm的矩阵上,一个格子(x,y)跳一步,将会到达(x1,y+1),(x,y+1),(x+1,y+1)中权值最大的格子(保证没有相同权值)。矩阵是循环的,例如从第m列往右边跳会到第1列,从第1行往上跳会到第n行,从第n行往下跳会到第1行。
一个棋子放在(1,1),现有两种操作:

  • 询问棋子从当前位置跳k步后到了哪
  • 修改某个格子的权值
    你需要回答所有询问

n,m2000,k109,操作数q5000,权值e109

分析

这题最关键的是要发现循环节。

一个点(x,y)最多跳nm遍就会跑进一个环里面。对于每个询问,我们用O(nm)的时间就能把这个环找出来,就不用暴力跳k步了。

现在的问题是,O(nm)找环还是太慢了,是否有更快的方法?

假设棋子从第一列出发,如果能一次跳m步,我们找环的时间就是O(n)了。所以,若棋子不在第一列,我们就暴力跳到第一列,时间复杂度是O(m)

那么如何维护第一列各行跳m步所到达行?DoggyZheng大爷提供了一种神奇的方法:

用一棵下标[1,m]的线段树,节点[l,r]开一个大小为n的数组,表示第l列各行跳rl+1步后到达了第r+1列的哪一行

线段树的根代表区间是[1,m],你要查询第一列某一行跳m步所到达行,直接用根的信息O(1)查询就行了。

线段树信息复杂度是O(n),每次修改更改节点数是O(logm),修改复杂度就是O(nlogm),由于此题时限4s,很轻松就过掉了。

总而言之,这题的关键点就两个:

  • 想到每次跳m步来更快找循环节
  • 用线段树维护

emmmm,很有价值的一道题。

Code

#include <cstdio>
#include <cstring>
#define F(i, l, r) for (int i = l; i <= r; ++i)
#define lson rt << 1
#define rson rt << 1 | 1

const int N = 2007;
inline int read()
{
	int x = 0, f = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
	return f ? -x : x;
}

int n, m, q, tx, ty, a[N][N], vis[N];
char opt;
int getnext(int x, int y)
{
	int x1 = x > 1 ? x - 1 : n, x2 = x < n ? x + 1 : 1, y1 = y < m ? y + 1 : 1;
	if (a[x1][y1] > a[x][y1] && a[x1][y1] > a[x2][y1]) return x1;
	if (a[x][y1] > a[x1][y1] && a[x][y1] > a[x2][y1]) return x;
	if (a[x2][y1] > a[x][y1] && a[x2][y1] > a[x1][y1]) return x2;
}

int go[N << 2][N];
void ins(int rt, int l, int r, int po, int c)
{
	if (l == r) { go[rt][c] = getnext(c, po); return; }
	int mid = l + r >> 1;
	if (po <= mid) ins(lson, l, mid, po, c);
	else ins(rson, mid + 1, r, po, c);
	F(i, 1, n) go[rt][i] = go[rson][go[lson][i]];
}
void build(int rt, int l, int r)
{
	if (l == r) { F(i, 1, n) go[rt][i] = getnext(i, l); return; }
	int mid = l + r >> 1;
	build(lson, l, mid), build(rson, mid + 1, r);
	F(i, 1, n) go[rt][i] = go[rson][go[lson][i]];
}

int main()
{
	freopen("jump.in", "r", stdin);
	freopen("jump.out", "w", stdout);
	n = read(), m = read();
	F(i, 1, n) F(j, 1, m) a[i][j] = read();
	build(1, 1, m);
	q = read(), tx = 1, ty = 1;
	while (q--)
	{
		scanf(" %c", &opt);
		if (opt == 'm')
		{
			int k = read(), nx, cnt = 0;
			while (k > 0 && ty != 1) tx = getnext(tx, ty), ty = ty < m ? ty + 1 : 1, k--;
			if (!k) { printf("%d %d\n", tx, ty); continue; }
			memset(vis, 0, sizeof(vis));
			nx = tx;
			while (!vis[nx]) vis[nx] = 1, nx = go[1][nx], cnt += m;
			while (k >= m && tx != nx) tx = go[1][tx], k -= m, cnt -= m;
			if (tx != nx) while (k > 0) tx = getnext(tx, ty), ty = ty < m ? ty + 1 : 1, k--;
			else
			{
				k %= cnt;
				while (k >= m) tx = go[1][tx], k -= m;
				while (k > 0) tx = getnext(tx, ty), ty = ty < m ? ty + 1 : 1, k--;
			}
			printf("%d %d\n", tx, ty);
		}
		else
		{
			int x = read(), y = read(), e = read(), x1 = x > 1 ? x - 1 : n, x2 = x < n ? x + 1 : 1, y1 = y > 1 ? y - 1 : m;
			a[x][y] = e, ins(1, 1, m, y1, x1), ins(1, 1, m, y1, x), ins(1, 1, m, y1, x2);
		}
	}
	return 0;
}

作者:zjlcnblogs

出处:https://www.cnblogs.com/zjlcnblogs/p/11305879.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   gz-gary  阅读(164)  评论(0编辑  收藏  举报
编辑推荐:
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
阅读排行:
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· 开发的设计和重构,为开发效率服务
· 从零开始开发一个 MCP Server!
· Ai满嘴顺口溜,想考研?浪费我几个小时
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示