题解:P12525 [Aboi Round 1] 私は雨
完全重置于 2025.11.12,hack 根本难不倒我。
写完看了一圈题解,我想说:
欸不是凭啥我不用卡常啊?
啊,吃完晚饭回到机房,有人说我题解被下了,我一看,确实。
所以说,到底还是要卡常数啊,下面新开一小节吧。
前言
这篇题解的复杂度是这样的:
小 \(p\) 是 \(O(q \sqrt n \log \sqrt n + n \sqrt V)\),大 \(p\) 是 \(O (q\sqrt V \log n)\)。
没卡常,也没有把 \(\log\) 调到下面什么的,过了。写完后到是卡了卡,暂时在最优解第一页。
疯狂卡常数,没加 hack 暂时是最优解。
正文
首先把值域看成一条数轴,我们发现查询的位置的总数直接和 \(p\) 相关,是 \(\lfloor \frac Vq \rfloor\) 次,容易想到根号分治,以 \(\sqrt V\) 作为分治边界。
对于 \(p > \sqrt V\),发现查询的值很少,可以直接存下每一个值的出现位置,存成一个个序列,然后在这些序列上二分,就可以得到答案。
这里给出 vector 的实现:
vector<int> pos[V]; // 每一个值的序列
// 查询
inline int query_ma(int l, int r, int vl, int vr, int p, int x) {
int que = (vl - x + p - 1) / p * p + x, res = 0;
while(que <= vr) {
if(pos[que].size())
res += upper_bound(pos[que].begin(), pos[que].end(), r) - lower_bound(pos[que].begin(), pos[que].end(), l);
que += p;
}
return res;
}
......
......
//(main 里面)存储
for(int i = 1; i <= n; i ++)
pos[in(a[i])].push_back(i);
对于 \(p \le \sqrt V\),可以对着序列分块,然后直接强行预处理某块内对模数 \(p\) 取模得数为 \(b\) 的值,全都压到一个 vector 里面,查询的时候仍旧二分查找,不过这次二分的内容是值域。
然后就发现会 MLE,很凄惨,所以干脆就分治边界开小一点,序列分块块长开大一点,让暴力多跑一点,复杂度是不纯粹了,不过还是能过的。
然后发现会 MLE,很凄惨,而且如果不跑标准块长就会 TLE,所以考虑优化空间,考虑不使用 vector,手写内存池,这样不仅跑得极快,而且空间更优秀。
这一步实现比较长,一会看完整代码吧。
然后是一些卡常技巧,毕竟一会要放的是我卡过常数的代码。
首先是 vector 的插入的常数非常大,大概是因为要动态分配内存,所以考虑先跑一遍你要插的数,求出每一个 vector 的大小,然后先 reverse 一下,就会飞快。
上面说了,不要用
vector,请手写。
然后是查询,一般人都会散块跑暴力对吧,这是正确的,但是可以优化。可以比较直接加上散块,以及加上整块容斥掉不想要的哪个运算更少,跑少的那个,这样可以把(普通分块的)查询从最劣 \(3\sqrt n\) 做成 最劣 \(2\sqrt n\),会快很多,在这里因为整块查询是带 \(\log\) 的,所以算运算次数的时候要带上查整块的代价,会更快一点点。
再然后是我看最上面和我做法(应该)一样的那个题解,\(p\) 小的部分他对每一块都 sort 了,而这一步显然可以通过使插入的数字提前有序而省去。
希望楼上也能活回来。
再再然后是注意操作的空间要连续,不然大概会慢,没试,我一开始写空间就是连续的。
补充一点,一开始我空间根本就不连续,用
vector会吃很多随机访问的常数,而手写就会好很多。
再再再然后是玄学调块长,很奇怪但是值域的分治边界改成 \(100\) 会快到飞起(作为对比我一开始是 \(200\))。
不要玄学卡块长,迟早被 hack。
再再再再然后是科学使用 unsigned short,这个也有很大优化。
没了。
被 hack 感言
该说算是迟来的卡常吗,回旋镖终于打回了我头上。
好在不难卡就是了,毕竟这些时间我也多学了些东西,用上就过了。
欸那我好像没什么感言了。
就这样吧,不说了。
完整代码(曾有过很多注释)
// code by 樓影沫瞬_Hz17
#include <bits/stdc++.h>
using namespace std;
#define getc() getchar_unlocked()
#define putc(a) putchar_unlocked(a)
#define en_ putc('\n')
#define e_ putc(' ')
using pii = pair<int, int>;
inline int in() {
int n = 0; char p = getc();
while(p < '-') p = getc();
do n = n * 10 + (p ^ 48), p = getc();
while(isdigit(p));
return n;
}
inline int in(int &a) { return a = in(); }
inline void out(int n) {
if(n > 9) out(n / 10);
putc(n % 10 + '0');
}
template<class T1, class T2> T1 max(T1 a, T2 b) { return a > b ? a : a = b;}
template<class T1, class T2> T1 min(T1 a, T2 b) { return a < b ? a : a = b;}
constexpr int N = 1e5 + 10, B = 650, T = N / B + 10, V = 2e5 + 10, D = 435, M = N / D + 10;
int n, a[N], ans, q;
char type;
inline void get(int &x) { x ^= type ? ans : 0;}
// 用到两次 vector 手写,这是其一
int szp[V], *stp[V], *edp[V], topp;
inline void reservep(int v, int sz) { stp[v] = edp[v] = &szp[topp]; topp += sz; }
int L[T], R[T];
unsigned short bel[N];
inline int query_ma(int l, int r, int vl, int vr, int p, int x) {
int que = (vl - x + p - 1) / p * p + x, res = 0;
while(que <= vr) {
if(stp[que] != edp[que])
res += upper_bound(stp[que], edp[que], r) - lower_bound(stp[que], edp[que], l);
que += p;
}
return res;
}
// 这是其二
int sz[D][D][T], pool[50000000], top;
int *st[D][D][T], *ed[D][D][T];
inline void reserve(int p, int les, int add, int sz) { ed[p][les][add] = st[p][les][add] = &pool[top]; top += sz; }
inline int query_mi(int l, int r, int vl, int vr, int p, int x, int **st, int **ed) {
int ql = bel[l], qr = bel[r], res = 0;
if(qr == ql) {
for(int i = l; i <= r; i ++)
if(a[i] % p == x)
res += (a[i] <= vr and a[i] >= vl);
return res;
} // 优化散块查询
if(R[ql] - l - 7 < l - L[ql]) {
for(int i = l; i <= R[ql]; i ++)
if(a[i] % p == x)
res += (a[i] <= vr and a[i] >= vl);
}
else {
if(st[ql] != ed[ql]) res += upper_bound(st[ql], ed[ql], vr) - lower_bound(st[ql], ed[ql], vl);
// 这个是整块查询,二分也就是
for(int i = L[ql]; i < l; i ++)
if(a[i] % p == x)
res -= (a[i] <= vr and a[i] >= vl);
}
if(r - L[qr] - 7 < R[qr] - r) {
for(int i = L[qr]; i <= r; i ++)
if(a[i] % p == x)
res += (a[i] <= vr and a[i] >= vl);
}
else {
if(st[qr] != ed[qr]) res += upper_bound(st[qr], ed[qr], vr) - lower_bound(st[qr], ed[qr], vl);
for(int i = r + 1; i <= R[qr]; i ++)
if(a[i] % p == x)
res -= (a[i] <= vr and a[i] >= vl);
}
for(int i = ql + 1; i < qr; i ++) res += upper_bound(st[i], ed[i], vr) - lower_bound(st[i], ed[i], vl);
return res;
}
struct num {
int id, val;
} b[N];
signed main() {
#ifndef ONLINE_JUDGE
freopen("私は雨.in", "r", stdin);
freopen("私は雨.out", "w", stdout);
#endif
in(n), type = in(); // 要让手写内存连续,提前算 size 不可避免
for(int i = 1; i <= n; i ++) szp[in(a[i])] ++;
for(int i = 1; i <= n; i ++) if(!stp[a[i]]) reservep(a[i], szp[a[i]]);
for(int i = 1; i <= n; i ++) *edp[a[i]] ++ = i;
for(int i = 1; i <= n; i ++) {
b[i].id = i;
b[i].val = a[i];
}
sort(b + 1, b + 1 + n, [](num&a, num&b) { return a.val < b.val; });
for(int i = 1, j; R[i - 1] != n; i ++) {
L[i] = (i - 1) * B + 1, R[i] = min(n, i * B);
for(j = L[i]; j <= R[i]; j ++) bel[j] = i;
}
for(int p = 1; p < D; p ++)
for(int i = 1; i <= n; i ++)
sz[p][a[i] % p][bel[i]] ++;
for(int p = 1; p < D; p ++)
for(int j = 0; j < p; j ++)
for(int i = 1; R[i - 1] != n; i ++)
reserve(p, j, i, sz[p][j][i]);
for(int p = 1; p < D; p ++)
for(int i = 1; i <= n; i ++)
*ed[p][b[i].val % p][bel[b[i].id]] ++ = b[i].val;
in(q);
for(int i = 1, l, r, L, R, p, x; i <= q; i ++) {
in(l), in(r), in(L), in(R), in(p), in(x);
if(type) get(l), get(r), get(L), get(R), get(p), get(x);
ans = p >= D ? query_ma(l, r, L, R, p, x) : query_mi(l, r, L, R, p, x, st[p][x], ed[p][x]);
out(ans), en_;
}
}
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~

浙公网安备 33010602011771号