Y
K
N
U
F

题解:P12525 [Aboi Round 1] 私は雨

link

完全重置于 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_;
	} 
}   
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~
posted @ 2025-10-21 19:49  樓影沫瞬_Hz17  阅读(51)  评论(14)    收藏  举报