P8264 [Ynoi Easy Round 2020] TEST_100 题解

题意:P8264 [Ynoi Easy Round 2020] TEST_100

简要题意:nn 个数的序列 a1,a2,,ana_1,a_2,\cdots,a_nqq 次询问,每次给定 l,r,vl,r,v,求依次访问 lrl \sim r 每一个位置 ii,执行 vvaiv \leftarrow \left| v-a_i \right|,最终的 vv 是什么。

范围:n,ai,v105n, a_i, v \leq 10^5。时空限制:22 秒,22 GB。

分块,设块长为 BB

假如我们可以预处理出每一个数经过每一个块最终的结果,那么我们就容易做到 O(qnB)O(\frac{qn}{B}) 询问答案了。问题在于怎么求这个。

首先,最暴力的直接枚举 v=0,1,2,,105v = 0, 1, 2, \cdots, 10^5,放进去跑,显然是不对的。考虑 vai\left| v-a_i \right| 有什么特性。他其实是一个分段函数,具体地,vai={vaivaiaivv<ai\left| v-a_i \right| = \begin{cases} v-a_i && v \geq a_i \\ a_i - v && v < a_i \end{cases}。于是我们可以确定,数 vv 经过这个块中每个数后的结果,必然是一个一次函数,不妨设为 f(v)=v×kv+bvf(v)=v\times k_v+b_v。进一步地,容易发现,kv=1k_v = 11-1

我们不妨设有一个函数 f(x)=xf(x)=x,初始定义域和值域都是 [0,105][0,10^5],动态维护当前函数的 kkbb。依次经过 aia_i 时,只有三种情况,分别是 aia_i 在值域左侧,右侧,或者之内。左侧和右侧的时候,我们发现分段函数其实只有一段,都可以修改函数表达式直接递归进下一个位置。但是在值域内的可能会被分为两段,如果分开,那么递归到下一个 ai+1a_{i+1} 需要分成两部分。

按照这个过程 DFS,我们得到了一个 O(nB×2B)O(\dfrac{n}{B} \times 2^B) 的复杂度。取 B=log2nB=\log_2 n 可以平衡到 O(n2log2n)O(\dfrac{n^2}{\log_2 n}) 的复杂度,但是显然过不去。

考虑优化,复杂度瓶颈显然在于 2B2^B 的处理部分。但我们容易注意到绝对值函数 f(x)=xvf(x) = \left| x-v \right|,呈一个 V 字形,对称的位置是 x=vx=v。所以其实上述过程中把函数分为两部分的过程,本质上只需要递归进定义域大小更大的一部分,另一部分直接对称求出即可。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <set>
#include <map>
using namespace std;

const int N = 1e5 + 5, M = 337;

int f[M][N];
int n, q, a[N];
int B;
int lans;

inline int get(int x)
{
	return x / B;
}

inline int getL(int x)
{
	return x * B;
}

int R;
int nowB;

void dfs(int l, int r, int k, int b, int u) // 定义域 [l,r]
{
	if (l > r) return;
	int zl = l * k + b, zr = r * k + b;
	if (zl > zr) swap(zl, zr);
	if (u == R)
	{
		for (int i = l; i <= r; i++)
		{
			f[nowB][i] = abs(i * k + b - a[R]);
		}
		return;
	}
	int p = a[u];
	if (p > zr) // x -> p - x
	{
		dfs(l, r, -k, p - b, u + 1);
	}
	else if (p < zl)
	{
		dfs(l, r, k, b - p, u + 1);
	}
	else
	{
		int place = (p - b);
		if (k >= 1)
		{
			int mid = place / k;
			// [l, mid], (mid, r]
			if (mid - l >= r - mid)
			{
				dfs(l, mid, -k, p - b, u + 1);
				int nj = mid - 1;
				for (int i = mid + 1; i <= r; i++)
				{
					f[nowB][i] = f[nowB][nj];
					nj--;
				}
			}
			else
			{
				dfs(mid, r, k, b - p, u + 1);
				int nj = mid + 1;
				for (int i = mid - 1; i >= l; i--)
				{
					f[nowB][i] = f[nowB][nj];
					nj++;
				}
			}
		}
		else
		{
			int mid = place / k;
			// [mid,r], [l,mid)
			if (r - mid >= mid - l)
			{
				dfs(mid, r, -k, p - b, u + 1);
				int nj = mid + 1;
				for (int i = mid - 1; i >= l; i--)
				{
					f[nowB][i] = f[nowB][nj];
					nj++;
				}
			}
			else
			{
				dfs(l, mid, k, b - p, u + 1);
				int nj = mid - 1;
				for (int i = mid + 1; i <= r; i++)
				{
					f[nowB][i] = f[nowB][nj];
					nj--;
				}
			}
		}
	}
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	B = sqrt(n);
	int maxn = get(n);
	memset(f, -1, sizeof f);
	for (int i = 0; i <= maxn; i++)
	{
		int l = max(1, getL(i));
		int r = min(n, getL(i + 1) - 1);
		R = r;
		nowB = i;
		dfs(0, (int)1e5, 1, 0, l);
	}
	while (q--)
	{
		int l, r, v;
		cin >> l >> r >> v;
		l ^= lans, r ^= lans, v ^= lans;
		if (get(l) == get(r))
		{
			int res = v;
			for (int i = l; i <= r; i++) res = abs(res - a[i]);
			cout << (lans = res) << "\n";
		}
		else
		{
			int gl = get(l), gr = get(r);
			int res = v;
			for (int i = l; i <= getL(gl + 1) - 1; i++) res = abs(res - a[i]);
			for (int i = gl + 1; i < gr; i++) res = f[i][res];
			for (int i = getL(gr); i <= r; i++) res = abs(res - a[i]);
			cout << (lans = res) << "\n";
		}
	}
	return 0;
}
posted @   HappyBobb  阅读(13)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示