P4117 [Ynoi2018] 五彩斑斓的世界
P4117 [Ynoi2018] 五彩斑斓的世界
给你一个长为 的序列 ,有 次操作:
- 把区间 中大于 的数减去 。
- 查询区间 中 的出现次数。
,时限 ,空限 。
sol
「突刺贯穿第二分块」。
难度评分:。
下面先假定 为值域。
先是分块照常套路,对于散块直接暴力,下面只考虑整块。
看一下询问,发现,把大于 的数减去 ,也就是把大于 ,小于等于区间最大值 的值 更改为 。
对于每一个整块建立一个并查集即可。
但是直接更改值复杂度是错的。
考虑 且 时,需进行 次操作。
再想一想,我们发现,把大于 的值减去 ,等价于把所有小于等于 的数全部加上 并区间减 。
考虑综合上述两种方法。
分治。
-
如果 ,使用第一种方法把大于 小于等于 的数 改为 ,复杂度为 。
-
如果 ,使用第二种方法把小于等于 的数 改为 并打上区间减 的标记,复杂度为 。
我们发现 是单调不增的,所以这部分是单块是 ,所有块是 的。
再看回并查集部分,
对于并查集,我们修改的时候,只需要把修改前值对应的并查集的根,连到修改后的值的并查集的根(记录的是块中第一次出现这个值的下标)上即可。同时我们需要记录每个数的出现次数,修改的时候直接加过去就好了。
再设一个 ,表示位置 的值,我们是方便通过并查集的根还原数组。
同时我们发现一个值被修改后,原值就消失了,因此这里并查集并不会进行路径压缩,是 的。
假设我们跑到一个位置,值为 ,下标为 ,分类讨论:
- 若无 ,跳过即可。
- 若无 ,将 改为 ,并设 的根为 。
- 若有 ,就把这个位置的根设为 的根(也就是之前出现过的 的位置)。
这部分是 的。
再回忆一下处理步骤:
- 对序列分块预处理。
- 对于一个块的全局修改、全局查询,按上述方法做即可,总时间复杂度为 。
- 对于一个块的部分修改,我们先暴力把每个位置的实际值还原,然后对块进行重构即可,单次为 。
- 对于一个块的部分查询,我们直接用并查集找到每个位置的实际值,然后判断是否相等即可,单次不超过 。
故总时间复杂度不超过 。
再分析空间复杂度,我们需要对每个块记录每个数的出现次数,那么至少需要一个 的数组,这非常不可接受。
不过我们可以发现,我们在分块的时候,块是独立的,块与块之间不会相互影响。
所以我们可以将操作离线,对每个块都按顺序处理一遍所有的操作,然后把询问的答案累加即可。
思考一下,这个 trick
为什么其他很多题都用不了呢?
因为用它要满足下面几个性质:
- 可以离线。
- 块是独立的,块与块之间不会相互影响。
- 答案可加性。
这样空间复杂度就优化成 了。
总结:
这题不是很卡常,但是用到了许多很有意思的 tricks
。
没了。
。
#include <cstdio>
#include <cmath>
#include <cstring>
namespace Fread
{
const int SIZE = 1 << 21;
char buf[SIZE], *S, *T;
inline char getchar()
{
if (S == T)
{
T = (S = buf) + fread(buf, 1, SIZE, stdin);
if (S == T)
return '\n';
}
return *S++;
}
}
namespace Fwrite
{
const int SIZE = 1 << 21;
char buf[SIZE], *S = buf, *T = buf + SIZE;
inline void flush()
{
fwrite(buf, 1, S - buf, stdout);
S = buf;
}
inline void putchar(char c)
{
*S++ = c;
if (S == T)
flush();
}
struct NTR
{
~NTR()
{
flush();
}
} ztr;
}
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#define putchar Fwrite::putchar
#endif
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
inline void write(int x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
inline int min(int x, int y)
{
return x < y ? x : y;
}
inline int max(int x, int y)
{
return x > y ? x : y;
}
const int _ = 1e6 + 7, V = 1e5 + 7;
int n, m, blo, len;
int a[_], fa[_], val[_], name[V], siz[V], lazy, maxx, ans[_];
struct que
{
int op, l, r, x;
} q[_];
inline int find(int x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void build(int x)
{
maxx = -2147483647 - 1, lazy = 0;
int bl = (x - 1) * blo + 1, br = x * blo;
if (x == len)
br = n;
for (int i = bl; i <= br; ++i)
{
maxx = max(maxx, a[i]);
if (!name[a[i]])
{
val[i] = a[i];
name[a[i]] = i;
fa[i] = i;
}
else
fa[i] = name[a[i]];
siz[a[i]]++;
}
}
inline void change(int x, int y)
{
if (name[y])
fa[name[x]] = name[y];
else
{
name[y] = name[x];
val[name[y]] = y;
}
siz[y] += siz[x];
name[x] = siz[x] = 0;
}
inline void update1(int num, int l, int r, int x)
{
int bl = (num - 1) * blo + 1, br = num * blo;
if (num == len)
br = n;
l = max(l, bl), r = min(r, br);
for (int i = bl; i <= br; ++i)
{
int w = val[find(i)];
a[i] = w - lazy;
name[w] = siz[w] = 0;
}
for (int i = bl; i <= br; ++i)
val[i] = 0;
for (int i = l; i <= r; ++i)
if (a[i] > x)
a[i] -= x;
build(num);
}
inline void update2(int x)
{
if (x > (maxx - lazy) / 2)
{
for (int i = x + lazy + 1; i <= maxx; ++i)
if (name[i])
change(i, i - x);
maxx = min(maxx, x + lazy);
}
else
{
for (int i = lazy; i <= x + lazy; ++i)
if (name[i])
change(i, i + x);
lazy += x;
}
}
inline int query(int num, int l, int r, int x)
{
int ans = 0, bl = (num - 1) * blo + 1, br = num * blo;
if (num == len)
br = n;
l = max(l, bl), r = min(r, br);
for (int i = l; i <= r; ++i)
if (val[find(i)] - lazy == x)
ans++;
return ans;
}
signed main()
{
n = read(), m = read();
blo = sqrt(n), len = ceil(n * 1.0 / blo);
for (int i = 1; i <= n; ++i)
a[i] = read();
for (int i = 1; i <= m; ++i)
{
q[i].op = read();
q[i].l = read();
q[i].r = read();
q[i].x = read();
}
for (int i = 1; i <= len; ++i)
{
if (i != 1)
{
memset(name, 0, sizeof name);
memset(siz, 0, sizeof siz);
}
lazy = 0, maxx = -2147483647 - 1;
int bl = (i - 1) * blo + 1, br = i * blo;
if (i == len)
br = n;
build(i);
for (int j = 1; j <= m; ++j)
{
int op = q[j].op, l = q[j].l, r = q[j].r, x = q[j].x;
if (bl > r || br < l)
continue;
if (op == 1)
{
if (!(l <= bl && br <= r))
update1(i, l, r, x);
else
update2(x);
}
else
{
if (x + lazy > 1e5 + 1)
continue;
if (!(bl >= l && br <= r))
ans[j] += query(i, l, r, x);
else
ans[j] += siz[x + lazy];
}
}
}
for (int i = 1; i <= m; ++i)
if (q[i].op == 2)
write(ans[i]), putchar('\n');
return 0;
}
本文来自博客园,作者:蒟蒻orz,转载请注明原文链接:https://www.cnblogs.com/orzz/p/18122045
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战