今天上来决定开始打 数颜色。
看算法标签,带个分块莫队,而且之前见的时候也是在分块专题。
看题,十分钟过去。。。。。
发现有 \(O(n \log^2 n)\) 时间,\(O(n)\) 空间的 CDQ 做法,显然严格优于带修莫队啊。
翻了翻题解,前面的有一个 \(O(n \log n)\) 空间的树套树,然后几乎全是分块莫队。
然后我就想,为啥都不写 CDQ 呢。
于是我决定打一个出来。
这就是为什么我这一个上午 + 一个下午啥都没干。
现在已经打了 233 行了,还差一部分没打。
而且打着打着也就意识到,这东西常数确实大到飞起。
另外,这么复杂的东西,如果是在考场上,我可能也需要考虑考虑是否要打。
仔细想想,其实综合来看不如带修莫队。
事实上,我之前做题写代码的习惯一直都是,尽可能使用复杂度更优秀的算法/实现方式。
现在,遇到的题越来越复杂,码力的提升速度远远比不上题目难度的增长。
况且,对于一些题目,相对于思考做法,打代码消耗的时间往往会更长。
所以,我们来考虑另一样东西吧,代码复杂度。
毕竟码力是有限的,不能抛开实现只考虑渐进复杂度。
即使时间无限,写的代码更长也意味着调试的时间更长,心态更爆炸。
如何在保证时空复杂度的前提下降低实现难度,是应该仔细考虑的。
文艺平衡树
写闲话写一半又回去做了个题。
treap 确实很好写啊。
但是我不喜欢平衡树。我也不知道为啥反正莫名其妙不喜欢。
所以我们来根号分治吧。
具体的,维护连续段。
每次分裂 \(O(1)\) 个连续段,然后把一段区间直接暴力翻转。
当连续段数量超过 \(O(\sqrt n)\) 的时候就把贡献直接统计到序列上,然后把连续段变回一个。
复杂度 \(O(n \sqrt n)\),常数不大,实现难度肉眼可见的小。
而且跑的确实非常快,内存也很小。
重点是不需要任何的算法或者数据结构,简直就是普及组实现难度。
当然,这东西的优越性主要体现在调试难度上。
给一个仍然有点冗长的代码吧。
#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
#define sz 100005
struct site
{
int lf, rt, x, y;
site(){}
site(int a, int b, int c, int d)
{
lf = a; rt = b;
x = c; y = d;
}
};
site ps[sz];
int n, m, big, top = 1;
int num[sz], tmp[sz];
void split(int, int);
void reload();
int main()
{
int x, y, savx, savy;
scanf ("%d %d", &n, &m);
big = sqrt(m) * 0.8;
for (int i = 1; i <= n; ++i)
num[i] = i;
ps[1] = site(1, n, 1, n);
for (int i = 1; i <= m; ++i)
{
scanf ("%d %d", &x, &y);
savx = x; savy = y;
for (int j = 1; j <= top; ++j)
{
if (ps[j].lf < x && ps[j].rt >= x)
split(j, x);
if (ps[j].lf <= y && ps[j].rt > y)
split(j, y + 1);
}
for (int j = 1; ; ++j)
if (ps[j].lf <= x && ps[j].rt >= x)
{
x = j;
for ( ; ; ++j)
if (ps[j].lf <= y && ps[j].rt >= y)
{
y = j;
break;
}
break;
}
for (int j = x, k = y; j < k; ++j, --k)
swap(ps[j], ps[k]);
for (int j = x; j <= y; ++j)
{
swap(ps[j].x, ps[j].y);
ps[j].lf = savy - (ps[j].lf - savx);
ps[j].rt = savy - (ps[j].rt - savx);
swap(ps[j].lf, ps[j].rt);
}
if (top > big)
reload();
}
reload();
for (int i = 1; i <= n; ++i)
printf ("%d ", num[i]);
return 0;
}
void split(int a, int b)
{
if (ps[a].x < ps[a].y)
{
ps[++top] = site(b, ps[a].rt, ps[a].x + b - ps[a].lf, ps[a].y);
ps[a].y = ps[a].x + b - ps[a].lf - 1;
}else
{
ps[++top] = site(b, ps[a].rt, ps[a].x - b + ps[a].lf, ps[a].y);
ps[a].y = ps[a].x - b + ps[a].lf + 1;
}
ps[a].rt = b - 1;
for (int i = top; i > 1; --i)
{
if (ps[i].lf > ps[i - 1].lf)
break;
swap(ps[i], ps[i - 1]);
}
}
void reload()
{
for (int i = 1; i <= n; ++i)
tmp[i] = num[i];
for (int i = 1; i <= top; ++i)
if (ps[i].x < ps[i].y)
for (int j = ps[i].lf, k = ps[i].x; j <= ps[i].rt; ++j, ++k)
num[j] = tmp[k];
else
for (int j = ps[i].lf, k = ps[i].x; j <= ps[i].rt; ++j, --k)
num[j] = tmp[k];
top = 1;
ps[1] = site(1, n, 1, n);
}
沾点学术就够了。后面开始发电。
进行这种思考和尝试,也许是心态的变化吧。
之前学 OI 的时候完全是抱着学术的心态,想要追求更加优秀的算法,多见一些有意思的东西。
所以当时学了不少例如线性RMQ、HLPP之类的冷门东西。
至于现在的话。。。
我确实已经好久没有学新东西了。
最开始学 OI 的时候确实可以抱着“只是来提升一下思维水平,感觉比文化课有意思”之类的理由。
现在目的基本上达到了。尤其是第二个。
但是,已经用掉了两年了。
只能说,空着手回去的话确实很亏。
具体原因的话。。。
无论什么东西,只要沾了考试,就难免被功利化和套路化。
高考就是一个很极端的例子。
老师学生苦心钻研的做题技巧,对于这个学科本身毫无帮助。只不过是应付考试罢了。
这也许是我不太喜欢文化课的原因之一吧。
我整体上还是倾向于纯粹的学术之类的。
初中的时候甚至没有听说过算法这个词,当时计划着以后去学物理。
然后被高中物理干碎了。
其实说白了,除了模拟计算就是模拟计算。用的是草稿纸,干的是电脑的活。
了解的越来越多之后才知道,HE 的这种现象比其他地区要明显很多。
毕竟在经济上是弱省,思想上比较保守,教育水平当然上不去。
也就 hz 这么个地方,看似闭塞,但是确实可以很大程度的了解外面。
以前经常会埋怨学校。
但是现在看,HE 出来这么个学校倒也合理。
想要在这么个地方和其他强省比,显然是需要代价的。
当然,学校该骂还是要骂,确实NT。
如果只是想安稳的生活的话,其实我家附近就很好。
工作压力不算大,没有加班一类的事情。
只要不浪费,挣的钱够用,生活大概是很安逸的。
但是我不是这么个喜欢安稳的人啊。至少现在不是。
想要个人发展,肯定不能在这么个地方。
在 HE 学奥赛,对思维水平一类的帮助大概是没有其他地方要大的。
毕竟教练也说了,靠的是暴力走天下。
所以,就不得不功利化一点了。
至少,先见一见外面的世界吧。
麻麻省的今天怎么写了这么多。
反正也没啥需要隐晦的,该写的都写了吧。