Y
K
N
U
F

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

link

顺带一提这是我第一道没看题解做出来的黑(也是第四道黑)。

写完看了一圈题解,我想说:

欸不是凭啥我不用卡常啊?

前言

这篇题解的复杂度是这样的:

\(p\)\(O(q \sqrt n \log \sqrt n + n \sqrt V)\),大 \(p\)\(O (q\sqrt V \log n)\)

没卡常,也没有把 \(\log\) 调到下面什么的,过了。写完后到是卡了卡,暂时在最优解第一页。

正文

首先把值域看成一条直线,我们发现查询的位置的总数直接和 \(p\) 相关,是 \(\lfloor \frac Vq \rfloor\) 次,容易想到根号分治,以 \(\sqrt V\) 作为分治边界。

对于 \(p > \sqrt V\),发现查询的值很少,可以直接存下每一个值的出现位置,存成一个个序列,然后在这些序列上二分,就可以得到答案。

这里给出实现:

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,很凄惨,所以干脆就分治边界开小一点,序列分块块长开大一点,让暴力多跑一点,复杂度是不纯粹了,不过还是能过的。

这一步实现比较长,一会看完整代码吧。

然后是一些卡常技巧,毕竟一会要放的是我卡过常数的代码。

首先是 vector 的插入的常数非常大,大概是因为要动态分配内存,所以考虑先跑一遍你要插的数,求出每一个 vector 的大小,然后先 reverse 一下,就会飞快。

然后是查询,一般人都会散块跑暴力对吧,这是正确的,但是可以优化。可以比较直接加上散块,以及加上整块容斥掉不想要的哪个运算更少,跑少的那个,这样可以把(普通分块的)查询从最劣 \(3\sqrt n\) 做成 最劣 \(2\sqrt n\),会快很多,在这里因为整块查询是带 \(\log\) 的,所以算运算次数的时候要带上查整块的代价,会更快一点。

再然后是我看最上面和我做法(应该)一样的那个题解,\(p\) 小的部分他对每一块都 sort 了,而这一步显然可以通过使插入的数字提前有序而省去。

再再然后是注意操作的空间要连续,不然大概会慢,没试,我一开始写空间就是连续的。

再再再然后是玄学调块长,很奇怪但是值域的分治边界改成 \(100\) 会快到飞起(作为对比我一开始是 \(200\))。

没了。

完整代码

// 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>;
 
template<class T> inline T in() { 
	T n = 0; char p = getc();
	while(p < '-') p = getc();
	bool f = p == '-' ? p = getc() : 0;
	do n = n * 10 + (p ^ 48), p = getc();
	while(isdigit(p));
	return f ? -n : n;
}
template<class T> inline T in(T &a) { return a = in<T>(); }
template<class T, class ... Args> inline void in(T &t, Args&... args) { in(t), in(args...); }
 
template<class T> inline void out(T n) {
	if(n < 0) putc('-'), n = -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 = 370, T = N / B + 10, V = 2e5 + 10, D = 100, M = N / D + 10;
 
int n, a[N], ans, q;
bool type;

vector<int> pos[V];

int L[T], R[T], bel[N];
// 大p的查询
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; // que 表示要查的位置,计算方式比较烂
	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;
}

int sz[D][D][T];
vector<int> sum[D][D][T]; // 写到这发现刚好是 DDT 给我笑喷了
// sum[i][j][k] 指在 k 块内对 i 取模为 j 的数
inline int que_mi(vector<int>&q, int vl, int vr) { 
	if(q.empty()) return 0; // 这个是 p 小的时候对整块的查询,用得多,兴许我不写成函数会快?我懒
	return upper_bound(q.begin(), q.end(), vr) - lower_bound(q.begin(), q.end(), vl);
} 
// 下面是对小 p 的查询
inline int query_mi(int l, int r, int vl, int vr, int p, int x, vector<int> *sm) { 
	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 - 14 < l - L[ql]) { // 令 14 作为整块查询的代价,比较是正着快还是容斥快
		for(int i = l; i <= R[ql]; i ++)  // 正着跑
			if(a[i] % p == x) 
				res += (a[i] <= vr and a[i] >= vl);
	} 
	else { 
		res += que_mi(sm[ql], vl, vr); // 容斥
		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] - 14 < 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 { 
		res += que_mi(sm[qr], vl, vr);
		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 += que_mi(sm[i], vl, vr);
	return res;
} 

struct num { int id, val; };
num b[N]; // 辅助数组

inline void get(int &x)  { x ^= type ? ans : 0;}

signed main() { 
	#ifndef ONLINE_JUDGE 
		freopen("私は雨.in", "r", stdin);
		freopen("私は雨.out", "w", stdout);
	#endif 
	in(n, type);
	for(int i = 1; i <= n; i ++) 
		pos[in(a[i])].push_back(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]] ++; // 预先算 size 

	for(int p = 1; p < D; p ++) 
		for(int j = 0; j < p; j ++) 
			for(int i = 1; R[i - 1] != n; i ++) 
				sum[p][j][i].reserve(sz[p][j][i]); // 然后 reserve

	for(int p = 1; p < D; p ++)  
		for(int i = 1; i <= n; i ++)  
			sum[p][b[i].val % p][bel[b[i].id]].emplace_back(b[i].val);
	// 如果不这样做的话会慢死的,预处理 TLE 了解一下
//直观讲一下就是不写的话跑到这里 1.13 s,写上就只用了 280 ms 左右
	in(q);

	for(int i = 1, l, r, L, R, p, x; i <= q; i ++) { 
		in(l, r, L, R, p, x);
		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, sum[p][x]);
		out(ans), en_;
	} 
}   
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~
posted @ 2025-10-21 19:49  樓影沫瞬_17Hz  阅读(9)  评论(10)    收藏  举报