Y
K
N
U
F

如果你做莫队题时不会标准根号复杂度……

前情提要:教练墙了 luogu,但是 cnblogs 没封……

肯定有你不会的莫队题对吧,一定的有对么?然后呢,这篇文章可以让你把一些你不会做 \(O(n\sqrt m)\) 的题目做成 \(O(n\sqrt m h)\)\(h\) 定义下面有说)或者 \(O(nm^\frac 23)\),没准能水过去呢!!!

所以以下均使用数列分块入门 9 作为例题。

正文

Part I:\(\log\) 的将就,\(O(n\sqrt m h)\)

一般我们看到一个用上了莫队但是又不太会 \(O(1)\) 移动指针的东西,我们怎么办。如果只是单纯好删不好加或好加不好删的话,那可以考虑回滚莫队,可是如果既不好删也不好加呢?也许会想到二次离线或看题解一个十分唐氏的东西:用 \(\log\) 数据结构辅助维护,然后复杂度变成难看的 \(n\sqrt m \log n\),写了这个复杂度你能过什么题?连典题区间众数都过不了好吗。

正如有人说过的:

\(\log\) 算法和根号八字不合。

所以复杂度注定不会非常好看(事实是难看到解不出来)。

至于思路,应该显然吧。

思考为什么分块经常和莫队搭档,因为人家可以 \(O(1)\) 修改,\(O(\sqrt n)\) 查询,和莫队的 \(O(n\sqrt m)\) 次修改,\(O(m)\) 次查询很搭。

拿线段树类比,它的单次修改复杂度是 \(O(\log n)\),查询也是 \(O(\log n)\),就很不搭,所以下面考虑强行平衡。

思考:为啥是 \(\log n\)?因为这是人家的层数,所以我们把层数拿出来,称其为 \(h\),然后你就可以……砍掉一些层数。

发现线段树维护信息的方式是“合并”+“上传”,只要你把一棵线段树上面砍掉,成为线段树森林,你的层数就会变小,至于查询?整棵线段树的查询显然是 \(O(1)\) 的,散的线段树最多两棵,带 \(\log\) 查就行,合并信息?手动合并呗,这样一来,你的查询复杂度就变成了 \(2h + \frac {n}{2^h}\)

然后你就可以微调 \(h\) 来平衡,大概就自己人工退火退一个一个跑得快的块长就行,一般考虑把 \(h\) 补成整数好些,因为这样线段树很整,实测速度也会块,而且非常方便 ZKW 线段树。然后就可以得到一个神秘的 \(O(n\sqrt m h)\) 复杂度。至于 \(h\)……反正是个位数,除非题很卡常,不然一般都能过。

注意:如果你不会 ZKW 线段树的话最好别用这个办法,毕竟递归线段树的常数摆着呢,用了也难卡过,看看下一节吧。

code

zxkqwq 写的特别快,我就算了,懒得卡常。

// code by 樓影沫瞬_Hz17
#include <bits/stdc++.h>
#define en_ putchar_unlocked('\n')
#define e_ putchar_unlocked(' ')
// #define int long long
using namespace std;
inline int in() { 
	int n = 0; char p = getchar_unlocked();
	while (p < '-') p = getchar_unlocked();
	bool f = p == '-' ? p = getchar_unlocked() : 0;
	do n = n * 10 + (p ^ 48), p = getchar_unlocked();
	while (p >= '0');
	return f ? -n : n;
}
inline int in(int &a) { return a = in(); }
inline void out(int n) {
	if(n < 0) putchar_unlocked('-'), n = -n;
	if(n > 9) out(n / 10);
	putchar_unlocked(n % 10 + '0');
}
constexpr int N = 3e5 + 10, B = 580, K = 256;
using pii = pair<int, int>;

struct query {
	int l, r, id;
} q[N];

int a[N], hs[N], pos[N], bel[N];

int L[N], R[N], cnt, n;

struct Tree {
	uint mx[K * 2], id[K * 2];
	#define ls (u << 1)
	#define rs (u << 1 | 1)
	inline void update(const int p) {
		uint u = p - L[bel[p]] + K;
		++ mx[u], id[u] = p;
		for(u >>= 1; u; u >>= 1) {
			if(mx[ls] >= mx[rs]) mx[u] = mx[ls], id[u] = id[ls];
			else mx[u] = mx[rs], id[u] = id[rs];
		}
	}
	inline void reduce(const int p) {
		uint u = p - L[bel[p]] + K;
		-- mx[u], id[u] = p;
		for(u >>= 1; u; u >>= 1) {
			if(mx[ls] >= mx[rs]) mx[u] = mx[ls], id[u] = id[ls];
			else mx[u] = mx[rs], id[u] = id[rs];
		}
	}
} rt[N / K + 10];


int ans[N];

signed main() {
	#ifndef ONLINE_JUDGE 
		freopen("in.ru", "r", stdin);
		freopen("out.ru", "w", stdout);
	#endif
	in(n);
	for(int i = 1; i <= n; i ++) hs[i] = in(a[i]);
	for(int i = 1; i <= n; i ++) in(q[i].l), in(q[i].r), q[i].id = i, pos[i] = (i - 1) / B +1;
	
	for(int i = 1; R[i - 1] != n; i ++) {
		L[i] = (i - 1) * K + 1, R[i] = min(i * K, n);
		for(int j = L[i]; j <= R[i]; j ++) bel[j] = i;
	}
	sort(q + 1, q + 1 + n, [](const query &a, const query &b) 
		{ return pos[a.r] == pos[b.r] ? pos[a.r] & 1 ? a.l < b.l : a.l > b.l : pos[a.r] < pos[b.r];});

	sort(hs + 1, hs + 1 + n);
	int len = unique(hs + 1, hs + 1+ n) - hs - 1;
	for(int i = 1; i <= n; i ++) 
		a[i] = lower_bound(hs + 1, hs + len + 1, a[i]) - hs;
	
	for(int i = 1, l = 1, r = 0, MX, ID; i <= n; i ++) {
		while(l > q[i].l) -- l, rt[bel[a[l]]].update(a[l]);
		while(r < q[i].r) ++ r, rt[bel[a[r]]].update(a[r]);
		while(l < q[i].l) rt[bel[a[l]]].reduce(a[l]), ++ l;
		while(r > q[i].r) rt[bel[a[r]]].reduce(a[r]), -- r;
		MX = 0;
		for(int i = 1; i <= bel[n]; i ++) {
			if(rt[i].mx[1] > MX) {
				MX = rt[i].mx[1];
				ID = rt[i].id[1];
			}
		}	
		ans[q[i].id] = hs[ID];
	}

	for(int i = 1; i <= n; i ++) out(ans[i]), en_;
} 
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~

Part II:个位数的块长,\(O(n m^{\frac {2}{3}})\)

这个倒是听说过呢。

如上所言,一般分块和莫队搭档都是十分好的,但是有时候,你发现:

就是如果你的修改、查询都只会做 \(\sqrt n\),难道要做 \(O(n\sqrt n\sqrt m)\) 吗……

同样的,令块长为 \(B\),发现你只会的是 \(O(B)\) 修改(暴力重构),\(O(\frac nB)\) 查询。

一个显然的思路是把块长改成 \(m^\frac 14\),复杂度变成 \(O(nm^\frac 34)\),但是这个还是很烂。

你可以考虑分两层块,也就是什么块套块,第一层块长 \(m^\frac 13\),第二层 \(m^\frac 16\),这样你的修改复杂度就变成了二倍常数的 \(O(m^\frac 16)\),查询就是 \(O(\frac {n}{m^\frac 13} )\),总的复杂度就是 \(O(nm^{\frac {2}{3}})\),发现这个比上面的那个 \(O(n\sqrt m h)\) 劣一点,实际表现(数列分块 \(9\) 为例)和 ZKW 线段树相近,远优于普通线段树。

然后就是标题,你发现一般 \(m^\frac 16\) 不会大于个位数……

然后这个码有 Solwek 的帮助(详见),特在此感谢一下。

code

Solwek 写的特别快,我就算了,懒得卡常。

// code by 樓影沫瞬_Hz17 & Solwek
#include <bits/stdc++.h>
#define en_ putchar_unlocked('\n')
#define e_ putchar_unlocked(' ')
using namespace std;
template<typename T> inline T in() { 
    T n = 0; char p = getchar_unlocked();
    while (p < '-') p = getchar_unlocked();
	bool f = p == '-' ? p = getchar_unlocked() : 0;
    do n = n * 10 + (p ^ 48), p = getchar_unlocked();
    while (isdigit(p));
    return f ? -n : n;
}
template<typename T> inline T in(T &a) { return a = in<T>(); }
template<typename T> inline void out(T n) {
    if(n < 0) putchar_unlocked('-'), n = -n;
	if(n > 9) out(n / 10);
    putchar_unlocked(n % 10 + '0');
}
const int N = 3e5 + 10, B = 577, B2 = 64, B3 = 8;
using pii = pair<int, int>;
 
int a[N], n, L1[N], R1[N], bel1[N], L2[N], R2[N], bel2[N];
int hs[N];
 
struct query {
	int l, r, id;
} q[N];
int pos[N];

int ans[N];
uint mx1[N], mx2[N], id1[N], id2[N], cnt[N];

inline void upd(int x,int v){
	cnt[x] += v;
	int id = bel2[x], idp = bel1[x];
	mx2[id] = 0;
	for(int i = L2[id]; i <= R2[id]; i ++) {
		if(cnt[i] > mx2[id]) {
			mx2[id] = cnt[i];
			id2[id] = i;
		}
	}	
	int idl = (idp - 1) * B3 + 1;
	int idr = min(bel2[n], idl + B3 - 1);
	mx1[idp] = 0;
	for(int i = idl; i <= idr; i ++) {  
		if(mx2[i] > mx1[idp]){
			mx1[idp] = mx2[i];
			id1[idp] = id2[i];
		}
	}	
}
signed main() {
	#ifndef ONLINE_JUDGE 
		freopen("in.ru", "r", stdin);
		freopen("out.ru", "w", stdout);
	#endif
	in(n);
	for(int i = 1; i <= n; i ++) hs[i] = in(a[i]);
	for(int i = 1; i <= n; i ++) in(q[i].l), in(q[i].r), q[i].id = i;
	for(int i = 1; i <= n; i ++) pos[i] = (i - 1) / B + 1;

	for(int i = 1; R1[i - 1] != n; i ++) {
		L1[i] = (i - 1) * B2 + 1, R1[i] = min(i * B2, n);
		for(int j = L1[i]; j <= R1[i]; j ++) bel1[j] = i;
	}
	for(int i = 1; R2[i - 1] != n; i ++) {
		L2[i] = (i - 1) * B3 + 1, R2[i] = min(i * B3, n);
		for(int j = L2[i]; j <= R2[i]; j ++) bel2[j] = i;
	}

	sort(q + 1, q + 1 + n, [](query a, query b) 
		{ return pos[a.l] == pos[b.l] ? pos[a.l] & 1 ? a.r < b.r : a.r > b.r : pos[a.l] < pos[b.l];});

	sort(hs + 1, hs + 1 + n);
	int len = unique(hs + 1, hs + 1+ n) - hs - 1;
	for(int i = 1; i <= n; i ++) 
		a[i] = lower_bound(hs + 1, hs + len + 1, a[i]) - hs;
	
	for(int i = 1, l = 1, r = 0, mx, id; i <= n; i ++) {
		while(l < q[i].l) upd(a[l], -1), l ++;
		while(l > q[i].l) l --, upd(a[l], 1);
		while(r < q[i].r) r ++, upd(a[r], 1);
		while(r > q[i].r) upd(a[r], -1), r --;
		mx = 0;
		for(int j = 1; j <= bel1[n]; j ++) {
			if(mx1[j] > mx) {
				mx = mx1[j];
				id = id1[j];
			}
		}
		ans[q[i].id] = hs[id];
	}

	for(int i = 1; i <= n; i ++) out(ans[i]), en_;
} 
// 星間~ 干渉~ 融解~ 輪迴~ 邂逅~ 再生~ ララバイ~
posted @ 2025-11-19 16:35  樓影沫瞬_Hz17  阅读(24)  评论(3)    收藏  举报