[Ynoi2007] rgxsxrs 题解
[Ynoi2007] rgxsxrs
终于 A 了这道题了,写篇题解纪念一下。/kel
本文的思路和代码源于 Ynoi rgxsxrs 题解 - CLZY 的博客
Description
给定一个长为 的序列 ,需要实现 次操作:
1 l r x
:表示将区间 中所有 的元素减去 。
2 l r
:表示询问区间 的和,最小值,最大值。
数据范围: 强制在线
Solution
主要是 倍增分块+线段树+底层分块 用到了一个 方法。
考虑最暴力的做法,直接建立一颗序列线段树,维护 ,对于每一个 1 操作
- 当前节点区间最大值 ,直接跳过。
- 当前节点区间最小值 , 打标记即可。
- 当前节点区间最小值 ,最大值 ,递归。
这样的时间复杂度显然是不行的,因为这样的过程会被无限递归到叶子节点。
考虑对于值域分块,这样我们可以大大减少对于取值的讨论。
发现对于取值的讨论主要产生在情况 3,那我们可不可以构造一种特殊的方式,让我们能够直接判断呢?
一个思路是只要 成立,那么这个块一定需要被修改。
即我们需要满足 对同一块内一切 的二元组成立, 最小取 ,那么 。
所以,这里我们采用一种特殊的分块方式,我们把值域分成 的形式,其中, 是进制。这样一共就是 块, 是值域。由上述的讨论,我们知道 可以为 的整数幂。
(为什么不直接定为 2 ?因为 小,那么块数会变多,一定地影响了时间复杂度,具体情况还需要接下来讨论)
每次区间下界 需要减去 时,对于一个块 :
- 若 ,则这个块中所有元素一定需要减去一个
- 若 ,则在这个块中的所有元素一定不需要减去一个
- 若 ,则这个块中最大的某几个元素需要减去一个
那么,我们可以对每个值域块内的所有下标,开不同的线段树维护,这样共 棵线段树
- 对每棵线段树维护区间 ,修改时若区间 减去 后不属于这个值域块,则会掉落到更低的值域块中,将其二分出来后移动到另一棵线段树中,由于 个块,所以这样的操作最多进行 次,剩下的元素进行一次区间
- 啥都不用做
- 对每棵线段树维护区间 ,若区间 ,则将其修改为减去 后的值,这里因为 在中,于是这样的操作对于每个数只会发生 次。
这样,跌落的最坏复杂度是
为了让 3 操作的时间复杂度尽量小,
又,这道题的复杂度和块长没有关系,故块数越少越好 。
也就是让复杂度 尽量小, 尽量大。
考虑极端情况, 尽可能的小,即 ,复杂度
的时候最优。
但是良心出题人lxl,良心地卡了一手空间。/kk
我们知道,空间复杂度瓶颈是线段树,考虑优化。
我们发现对于一棵线段树,最占空间的是比较底层的节点,尤其是叶子。
于是我们可以设置一个常数 ,让线段树 的区间的处理直接变成下传标记在序列上暴力,之后再上传。我们可以这样很可观地以大常数的时间代价换取空间。
实测最优,总空间线性。
Code
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
#define per(i, r, l) for (int i = (r); i >= (l); --i)
#define ls (num << 1)
#define rs (num << 1 | 1)
const int inf = INT_MAX, df = 2e4 + 7, N = 5e5 + 7, B = 16, K = 32 ;/*相对较好的参数*/
int i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;
long long ma[9][df << 1];
long long tag[9][df << 1];
int mx[9][df << 1], mn[9][df << 1];
int a[N];
long long sum[9][df << 1];
long long lb[9], rb[9]; /*这里是倍增分块的上界和下界*/
/*
ma:区间有几个数
sum:区间和
mx:区间最大值
mn:区间最小值
tag:懒标记
*/
void pushup(int blk, int num)
{
ma[blk][num] = ma[blk][ls] + ma[blk][rs];
sum[blk][num] = sum[blk][ls] + sum[blk][rs];
mx[blk][num] = max(mx[blk][ls], mx[blk][rs]);
mn[blk][num] = min(mn[blk][ls], mn[blk][rs]);
return;
}
/*线段树上传信息*/
void pushdown(int blk, int num)
{
if (!tag[blk][num])
return;
tag[blk][ls] += tag[blk][num] * (ma[blk][ls] != 0), tag[blk][rs] += tag[blk][num] * (ma[blk][rs] != 0);
if (ma[blk][ls])
sum[blk][ls] -= tag[blk][num] * ma[blk][ls], mx[blk][ls] -= tag[blk][num], mn[blk][ls] -= tag[blk][num];
if (ma[blk][rs])
sum[blk][rs] -= tag[blk][num] * ma[blk][rs], mx[blk][rs] -= tag[blk][num], mn[blk][rs] -= tag[blk][num];
tag[blk][num] = 0;
return;
}
/*线段树下传标记*/
int getblk(int x)
{
int now = 1;
while (x > rb[now])
now++;
return now;
}
/*x在第几个值域块*/
void blk_pushdown(int blk, int le, int rig, long long &x)
{
if (!x)
return;
rep(i, le, rig) if (a[i] >= lb[blk] && a[i] <= rb[blk]) a[i] -= x;
x = 0;
return;
}
/*线段树的叶子节点向序列下传标记*/
void blk_pushup(int blk, int le, int rig, int num)
{
ma[blk][num] = 0, sum[blk][num] = 0, mx[blk][num] = 0, mn[blk][num] = inf;
rep(i, le, rig)
{
if (a[i] >= lb[blk] && a[i] <= rb[blk])
{
ma[blk][num]++;
sum[blk][num] += a[i];
mx[blk][num] = max(mx[blk][num], a[i]);
mn[blk][num] = min(mn[blk][num], a[i]);
}
}
return;
}
/*把序列的信息上传到线段树的叶子节点上*/
void insert(int blk, int num, int le, int rig, int p, int x)
{
if (rig - le + 1 <= K)
{
blk_pushdown(blk, le, rig, tag[blk][num]);
a[p] = x;
blk_pushup(blk, le, rig, num);
return;
}
int mid = (le + rig) >> 1;
pushdown(blk, num);
if (p <= mid)
insert(blk, ls, le, mid, p, x);
else
insert(blk, rs, mid + 1, rig, p, x);
pushup(blk, num);
return;
}
/*线段树插入节点*/
void blk_upd(int blk, int le, int rig, int x)
{
rep(i, le, rig) if (a[i] >= lb[blk] && a[i] <= rb[blk] && a[i] > x)
{
a[i] -= x;
if (a[i] < lb[blk])
{
insert(getblk(a[i]), 1, 1, n, i, a[i]);
} /*插入另一个块的线段树*/
}
return;
}
/*块内暴力执行1号操作*/
void blk_qry(int blk, long long &ans, int &maxn, int &minn, int le, int rig, int l, int r)
{
rep(i, max(le, l), min(rig, r))
{
if (a[i] >= lb[blk] && a[i] <= rb[blk])
{
ans += a[i], maxn = max(maxn, a[i]), minn = min(minn, a[i]);
}
}
return;
}
/*块内暴力执行查询*/
void upd(int blk, int num, int le, int rig, int l, int r, int x)
{
if (mx[blk][num] <= x)
return; /*小于就跳过*/
if (rig - le + 1 <= K)
{
blk_pushdown(blk, le, rig, tag[blk][num]);
l = max(l, le), r = min(r, rig);
blk_upd(blk, l, r, x);
blk_pushup(blk, le, rig, num);
return;
} /*到达叶子暴力修改*/
if (le >= l && rig <= r && mn[blk][num] - lb[blk] >= x)
{
sum[blk][num] -= x * ma[blk][num], mn[blk][num] -= x, mx[blk][num] -= x; /*直接打懒标记*/
tag[blk][num] += x;
return;
}
int mid = (le + rig) >> 1;
pushdown(blk, num);
if (l <= mid)
upd(blk, ls, le, mid, l, r, x);
if (r > mid)
upd(blk, rs, mid + 1, rig, l, r, x);
pushup(blk, num);
return;
}
/*线段树上执行操作*/
void qry(long long &ans, int &maxn, int &minn, int blk, int num, int le, int rig, int l, int r)
{
if (le >= l && rig <= r)
{
ans += sum[blk][num], maxn = max(maxn, mx[blk][num]), minn = min(minn, mn[blk][num]);
return;
} /*叶子节点暴力查询*/
if (rig - le + 1 <= K)
{ /*完全包含,直接统计*/
blk_pushdown(blk, le, rig, tag[blk][num]);
blk_qry(blk, ans, maxn, minn, le, rig, l, r);
blk_pushup(blk, le, rig, num);
return;
}
int mid = (le + rig) >> 1;
pushdown(blk, num); /*注意及时下传和上传*/
if (l <= mid)
qry(ans, maxn, minn, blk, ls, le, mid, l, r);
if (r > mid)
qry(ans, maxn, minn, blk, rs, mid + 1, rig, l, r);
pushup(blk, num);
return;
}
inline int read()
{
int x = 0, y = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
y = (ch == '-') ? -1 : 1, ch = getchar();
while (ch >= '0' && ch <= '9')
x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * y;
}
void fwrite(int x)
{
if (x > 9)
fwrite(x / 10);
putchar(x % 10 + '0');
return;
} /*最好加上快读快输*/
void llfwrite(long long x)
{
if (x > 9)
llfwrite(x / 10);
putchar(x % 10 + '0');
return;
}
int main()
{
rep(j, 1, 8) rep(i, 0, df * 2 - 2) mn[j][i] = inf, mx[j][i] = 0;/*初始化*/
n = read(), q = read();
rep(i, 1, 8) lb[i] = rb[i - 1] + 1, rb[i] = lb[i] * B - 1;/*预处理*/
rep(i, 1, n) a[i] = x = read(), insert(getblk(x), 1, 1, n, i, x);
int op = 0, l = 0, r = 0, x = 0, lastans = 0, maxn = 0, minn = 0;
long long ans = 0;
while (q--)
{
op = read(), l = read() ^ lastans, r = read() ^ lastans;
if (op == 1)
{
x = read() ^ lastans;/*强制在线*/
rep(i, 1, 8) if (rb[i] >= x) upd(i, 1, 1, n, l, r, x);
}
else
{
ans = 0, maxn = 0, minn = inf;
rep(i, 1, 8) qry(ans, maxn, minn, i, 1, 1, n, l, r);
llfwrite(ans), putchar(' '), fwrite(minn), putchar(' '), fwrite(maxn), putchar('\n');
lastans = ans % 1048576;
}
}
}
Extra
这道题是需要卡常的,所以介绍一个卡常小技巧(虽然上面没有用到)
计算机有一个缓存机制大致是:先把所有内存块对齐,当我们访问到一个点的时候,会把相应块中所有加入缓存。而缓存有内存限制,会按某种机制更新缓存,缓存其实就是一个比较小的内存,访问缓存比访问普通内存更快。
所以,线段树用结构体写,比用数组要快
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】