莫队

莫队

假设 \(n, m\) 同阶,对于序列上的区间询问问题,如果得知 \([l, r]\) 的答案,可以在 \(O(1)\) 的时间推算出 \([l - 1, r], [l + 1, r], [l, r - 1], [l, r + 1]\) 的答案,那么我们就可以在 \(O(n \sqrt{n})\) 的时间求出所有询问的答案。

普通莫队

实现

将所有的询问离线后以左端点所在的块为第一关键字,右端点为第二关键字进行排序。按排序后的顺序处理每一个询问,暴力从上一个区间的答案推出当前区间的答案。

注意移动指针时应先扩大区间, 后缩小区间。

时间复杂度 \(O(n \sqrt{n})\)

for (int i = 1, l = 1, r = 0; i <= m; ++i) {
    while (l > qry[i].l)
        add(--l);

    while (r < qry[i].r)
        add(++r);
    
    while (l < qry[i].l)
        del(l++);

    while (r > qry[i].r)
        del(r--); // 先扩大区间, 后缩小区间

    ans[qry[i].id] = result;
}

优化

调整块长

在随机数据下,块长设为 \(\dfrac{n}{\sqrt{m}}\)​ 会快一些。

奇偶性排序

在选择奇数块时,按照右端点从小到大排序,在偶数块时从大到小排序,这样可以减少右端点进行大跳动的次数,常数优化为原来的 \(\dfrac{1}{2}\)

原理:因为右指针移动到右边后就不需要跳回左边了,而跳回左边后处理下一个块是要跳动到右边的。

应用

SP3267 DQUERY - D-query

\(q\) 次询问区间数字种数。

\(n \leq 3 \times 10^4, q \leq 2 \times 10^5, a_i \leq 10^6\)

模板,下给出参考代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 3e4 + 7, Q = 2e5 + 7, V = 1e6 + 7;

struct Query {
	int l, r, bid, *ans;
	
	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : bid < rhs.bid;
	}
} qry[Q];

int a[N], cnt[V], ans[Q];

int n, q, Answer;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void add(int x) {
	if (!cnt[a[x]])
		++Answer;
	
	++cnt[a[x]];
}

inline void del(int x) {
	--cnt[a[x]];
	
	if (!cnt[a[x]])
		--Answer;
}

signed main() {
	n = read();
	
	for (int i = 1; i <= n; ++i)
		a[i] = read();
	
	q = read();
	int blocksiz = n / sqrt(q) + 1;
	
	for (int i = 1; i <= q; ++i) {
		qry[i].l = read(), qry[i].r = read();
		qry[i].bid = qry[i].l / blocksiz, qry[i].ans = ans + i;
	}
	
	sort(qry + 1, qry + 1 + q);
	
	for (int i = 1, l = 1, r = 0; i <= q; ++i) {
		while (l > qry[i].l)
			add(--l);
		
		while (r < qry[i].r)
			add(++r);
		
		while (l < qry[i].l)
			del(l++);
		
		while (r > qry[i].r)
			del(r--);
		
		*qry[i].ans = Answer;
	}
	
	for (int i = 1; i <= q; ++i)
		printf("%d\n", ans[i]);
	
	return 0;
}

P2709 小B的询问

\(q\) 次询问区间数字出现次数平方和。

\(n, m, a_i \leq 5 \times 10^4\)

因为 \(a_i\) 的值域只有 \(5 \times10^4\) ,所以我们开个桶记录每个数出现次数。

注意到:

\[(a+1)^2=a^2+2a+1 \]

于是我们就可以很轻松地设计 add 函数和 del 函数了。

P1494 [国家集训队] 小 Z 的袜子

\(m\) 次询问区间内任选两个数相同的概率。

\(n, m \leq 5 \times 10^4, a_i \leq n\)

将题意用式子可以表示为:

\[\cfrac{\sum \dfrac{cnt_k(cnt_k - 1)}{2}}{\dfrac{len(len-1)}{2}} = \dfrac{\sum cnt_k^2 - \sum cnt_k}{len(len-1)} = \dfrac{\sum cnt_k^2 - len}{len(len-1)} \]

问题就转化为求区间数字出现次数平方和。

CF617E XOR and Favorite Number

给定 \(k\)\(m\) 次询问区间内有多少子段异或和为 \(k\)

\(n, m \leq 10^5\)\(k, a_i \leq 10^6\)

可以先预处理出前缀异或值,由前缀和性质可知,问题转化为有多少对 \(i,j\) 满足 \(s_i \oplus s_j = k\) 。注意到 \(x \oplus y = k\) 等价于 \(x \oplus k = y\) 。于是问题转化为区间数字出现次数。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, V = 2e6 + 7;

struct Query {
	int l, r, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
	}
} qry[N];

ll ans[N];
int a[N], cnt[V];

ll result;
int n, m, k, block;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void add(int x) {
	result += cnt[a[x] ^ k], ++cnt[a[x]];
}

inline void del(int x) {
	--cnt[a[x]], result -= cnt[a[x] ^ k];
}

signed main() {
	n = read(), m = read(), k = read(), block = n / sqrt(m) + 1;

	for (int i = 1; i <= n; ++i)
		a[i] = a[i - 1] ^ read();

	for (int i = 1; i <= m; ++i)
		qry[i].l = read() - 1, qry[i].r = read(), qry[i].id = i, qry[i].bid = qry[i].l / block;

	sort(qry + 1, qry + 1 + m);

	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		while (l > qry[i].l)
			add(--l);

		while (r < qry[i].r)
			add(++r);

		while (l < qry[i].l)
			del(l++);

		while (r > qry[i].r)
			del(r--);

		ans[qry[i].id] = result;
	}

	for (int i = 1; i <= m; ++i)
		printf("%lld\n", ans[i]);

	return 0;
}

带修莫队

实现

强行加上一维时间维,表示该操作的时间。在查询时修改,改多了就还原回来,改少了就改过去。

\(n\) 为序列长度,\(m\) 个询问, \(t\) 个修改,则最佳块长为 \(\sqrt[3]{\dfrac{n^2 t}{m}}\) ,实际操作取块长为 \(n^{\frac{2}{3}}\) 即可。当 \(n, m, t\) 同数量级时时间复杂度为 \(O(n^{\frac{5}{3}})\)

应用

P1903 [国家集训队] 数颜色 / 维护队列

\(m\) 次操作:

  • Q l r :求 \(a_{l, r}\) 中颜色总数。
  • R p c :将 \(a_p\) 改为颜色 \(c\)

\(n, m \leq 1.5 \times 10^5, a_i \leq 10^6\)

模板,下给出参考代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 1.4e5 + 7, V = 1e6 + 7;

struct Update {
    int pos, val;
} upd[N];

int bel[N];

struct Query {
    int l, r, t, id;
    
    inline bool operator < (const Query &rhs) const {
        return bel[l] == bel[rhs.l] ? (bel[r] == bel[rhs.r] ? t < rhs.t : r < rhs.r) : l < rhs.l;
    }
} qry[N];

int a[N], cnt[V], ans[N];

int n, m, block, cntC, cntQ, result;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

inline char readc() {
    char c = getchar();

    while (c != 'Q' && c != 'R')
        c = getchar();

    return c;
}

inline void add(int x) {
    if (!cnt[x])
        ++result;
    
    ++cnt[x];
}

inline void del(int x) {
    --cnt[x];
    
    if (!cnt[x])
        --result;
}

inline void update(int t, Query cur) {
    if (cur.l <= upd[t].pos && upd[t].pos <= cur.r)
        del(a[upd[t].pos]), add(upd[t].val);
    
    swap(upd[t].val, a[upd[t].pos]);
}

signed main() {
    n = read(), m = read(), block = pow(n, 0.667) + 1;
    
    for (int i = 1; i <= n; ++i)
        a[i] = read(), bel[i] = i / block;
    
    for (int i = 1; i <= m; ++i) {
        if (readc() == 'R')
            upd[++cntC].pos = read(), upd[cntC].val = read();
        else
            qry[++cntQ].l = read(), qry[cntQ].r = read(), qry[cntQ].t = cntC, qry[cntQ].id = cntQ;
    }
    
    sort(qry + 1, qry + 1 + cntQ);
    
    for (int i = 1, l = 1, r = 0, t = 0; i <= cntQ; ++i) {
        while (l > qry[i].l)
            add(a[--l]);
        
        while (r < qry[i].r)
            add(a[++r]);
        
        while (l < qry[i].l)
            del(a[l++]);
        
        while (r > qry[i].r)
            del(a[r--]);
        
        while (t < qry[i].t)
            update(++t, qry[i]);
        
        while (t > qry[i].t)
            update(t--, qry[i]);
        
        ans[qry[i].id] = result;
    }
    
    for (int i = 1; i <= cntQ; ++i)
        printf("%d\n", ans[i]);
    
    return 0;
}

树上莫队

实现

考虑将树的括号序跑下来,在括号序上跑莫队。

设点 \(i\) 入栈时时间戳为 \(st_i\) ,出栈时时间戳为 \(ed_i\) ,则对于查询 \(u \to v\) 路径上的信息分类讨论(钦定 \(dfn_u < dfn_v\) ):

  • \(u\)\(v\) 的祖先:查询 \([st_u, st_v]\) 即可。
  • \(u\) 不是 \(v\) 的祖先:查询 \([ed_u, st_v]\)\(LCA(u, v)\) 即可。

注意查询时应忽略出现两次的点,括号序要开两倍内存。

时间复杂度和普通莫队时一样的。

应用

COT2 - Count on a tree II

给定一棵 \(n\) 个点的树,\(m\) 次查询 \(u \to v\) 路径上节点颜色种数。

\(n \leq 4 \times 10^4, m \leq 10^5\)

模板,下给出参考代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;

struct Graph {
	vector<int> e[N];
	
	inline void insert(int u, int v) {
		e[u].emplace_back(v);
	}
} G;

struct Query {
	int l, r, lca, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
	}
} qry[N];

int a[N], fa[N], dep[N], siz[N], son[N], top[N], st[N], ed[N], seq[N], cnt[N], ans[N];
bool tag[N];

int n, m, block, dfstime, result;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

void dfs1(int u, int f) {
	fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
	int mxsiz = -1;

	for (int v : G.e[u]) {
		if (v == f)
			continue;

		dfs1(v, u), siz[u] += siz[v];

		if (siz[v] > mxsiz)
			son[u] = v, mxsiz = siz[v];
	}
}

void dfs2(int u, int topf) {
	top[u] = topf, seq[st[u] = ++dfstime] = u;

	if (son[u])
		dfs2(son[u], topf);

	for (int v : G.e[u])
		if (v != fa[u] && v != son[u])
			dfs2(v, v);

	seq[ed[u] = ++dfstime] = u;
}

inline int LCA(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);

		x = fa[top[x]];
	}

	return dep[x] < dep[y] ? x : y;
}

inline void add(int x) {
	if (!cnt[a[x]])
		++result;

	++cnt[a[x]];
}

inline void del(int x) {
	--cnt[a[x]];

	if (!cnt[a[x]])
		--result;
}

inline void update(int x) {
	if (tag[x])
		del(x), tag[x] = false;
	else
		add(x), tag[x] = true;
}

signed main() {
	n = read(), m = read();
	vector<int> vec;

	for (int i = 1; i <= n; ++i)
		vec.emplace_back(a[i] = read());

	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());

	for (int i = 1; i <= n; ++i)
		a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

	for (int i = 1; i < n; ++i) {
		int u = read(), v = read();
		G.insert(u, v), G.insert(v, u);
	}

	dfs1(1, 0), dfs2(1, 1);
	block = n * 2 / sqrt(m * 0.667);

	for (int i = 1; i <= m; ++i) {
		int x = read(), y = read();

		if (st[x] > st[y])
			swap(x, y);

		qry[i].id = i, qry[i].lca = LCA(x, y);

		if (qry[i].lca == x)
			qry[i].l = st[x], qry[i].r = st[y], qry[i].lca = 0;
		else
			qry[i].l = ed[x], qry[i].r = st[y];

		qry[i].bid = qry[i].l / block;
	}

	sort(qry + 1, qry + 1 + m);

	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		while (l > qry[i].l)
			update(seq[--l]);

		while (r < qry[i].r)
			update(seq[++r]);

		while (l < qry[i].l)
			update(seq[l++]);

		while (r > qry[i].r)
			update(seq[r--]);

		if (qry[i].lca)
			update(qry[i].lca);

		ans[qry[i].id] = result;

		if (qry[i].lca)
			update(qry[i].lca);
	}

	for (int i = 1; i <= m; ++i)
		printf("%d\n", ans[i]);

	return 0;
}

P4074 [WC2013] 糖果公园

给定一棵树,\(q\) 次询问 \(u \to v\) 路径上 \(\sum a_{c_i} \sum_{j = 1}^{cnt_{c_i}} w_i\) 的值,其中 \(a_{c_i}\) 表示颜色 \(c_i\) 的价值,\(cnt_{c_i}\) 表示 \(c_i\) 出现次数,\(w_i\) 表示出现 \(i\) 次的价值。

\(n, m, q \leq 10^5\)

树上带修莫队模板,下给出参考代码。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

struct Graph {
	vector<int> e[N];
	
	inline void insert(int u, int v) {
		e[u].emplace_back(v);
	}
} G;

struct Update {
	int pos, val;
} upd[N];

struct Query {
	int l, r, lca, t, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
	}
} qry[N];

ll ans[N];
int fa[N], dep[N], siz[N], son[N], top[N];
int st[N], ed[N], seq[N], cnt[N];
int v[N], w[N], c[N];
bool tag[N];

ll result;
int n, m, q, block, dfstime, cntC, cntQ;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

void dfs1(int u, int f) {
	fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
	int mxsiz = -1;

	for (int v : G.e[u]) {
		if (v == f)
			continue;

		dfs1(v, u), siz[u] += siz[v];

		if (siz[v] > mxsiz)
			son[u] = v, mxsiz = siz[v];
	}
}

void dfs2(int u, int topf) {
	top[u] = topf, seq[st[u] = ++dfstime] = u;

	if (son[u])
		dfs2(son[u], topf);

	for (int v : G.e[u])
		if (v != fa[u] && v != son[u])
			dfs2(v, v);

	seq[ed[u] = ++dfstime] = u;
}

inline int LCA(int x, int y) {
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]])
			swap(x, y);

		x = fa[top[x]];
	}

	return dep[x] < dep[y] ? x : y;
}

inline void update(int x) {
	if (tag[x])
		result -= 1ll * v[c[x]] * w[cnt[c[x]]--], tag[x] = false;
	else
		result += 1ll * v[c[x]] * w[++cnt[c[x]]], tag[x] = true;
}

inline void modify(int t) {
	if (tag[upd[t].pos])
		update(upd[t].pos), swap(c[upd[t].pos], upd[t].val), update(upd[t].pos);
	else
		swap(c[upd[t].pos], upd[t].val);
}

signed main() {
	n = read(), m = read(), q = read();

	for (int i = 1; i <= m; ++i)
		v[i] = read();

	for (int i = 1; i <= n; ++i)
		w[i] = read();

	for (int i = 1; i < n; ++i) {
		int u = read(), v = read();
		G.insert(u, v), G.insert(v, u);
	}

	for (int i = 1; i <= n; ++i)
		c[i] = read();

	dfs1(1, 0), dfs2(1, 1);
	block = n * 2 / sqrt(q * 0.667);

	for (int i = 1; i <= q; ++i) {
		int op = read(), x = read(), y = read();

		if (op) {
			if (st[x] > st[y])
				swap(x, y);

			qry[++cntQ].t = cntC, qry[cntQ].id = cntQ, qry[cntQ].lca = LCA(x, y);

			if (qry[cntQ].lca == x)
				qry[cntQ].l = st[x], qry[cntQ].r = st[y], qry[cntQ].lca = 0;
			else
				qry[cntQ].l = ed[x], qry[cntQ].r = st[y];

			qry[cntQ].bid = qry[cntQ].l / block;
		} else
			upd[++cntC].pos = x, upd[cntC].val = y;
	}

	sort(qry + 1, qry + 1 + cntQ);

	for (int i = 1, l = 1, r = 0, t = 0; i <= cntQ; ++i) {
		while (t < qry[i].t)
			modify(++t);

		while (t > qry[i].t)
			modify(t--);

		while (l > qry[i].l)
			update(seq[--l]);

		while (r < qry[i].r)
			update(seq[++r]);

		while (l < qry[i].l)
			update(seq[l++]);

		while (r > qry[i].r)
			update(seq[r--]);

		if (qry[i].lca)
			update(qry[i].lca);

		ans[qry[i].id] = result;

		if (qry[i].lca)
			update(qry[i].lca);
	}

	for (int i = 1; i <= cntQ; ++i)
		printf("%lld\n", ans[i]);

	return 0;
}

回滚莫队

大前提:一类可离线问题。

  • 有些信息区间伸长时很好维护,区间缩短时却不好维护。
  • 有些信息区间缩短时很好维护,区间伸长时却不好维护。

实现

只增不删回滚莫队

首先将询问排序,应保证左端点在同一块内的询问的右端点单调不降。

若区间端点在同一块内,直接暴力解决即可。

否则每次处理询问时,令 \(l\) 指针指向块尾,\(r\) 指针指向块尾。先移动 \(r\) 指针,由于右端点不降,于是只要插入即可。保留下来信息,后移动 \(l\) 指针求解,处理完后用前面保留下来的信息恢复即可。

只删不增回滚莫队

首先将询问排序,应保证左端点在同一块内的询问的右端点单调不升。

若区间端点在同一块内,直接暴力解决即可。

否则每次处理询问时,令 \(l\) 指针指向块头,\(r\) 指针指向 \(n\) 。先移动 \(r\) 指针,由于右端点不升,于是只要删除即可。保留下来信息,后移动 \(l\) 指针求解,处理完后用前面保留下来的信息恢复即可。

时间复杂度分析

设分块大小为 \(B\) ,则时间复杂度为 \(O(mB + \dfrac{n^2}{B})\) ,当 \(B= \dfrac{n}{\sqrt{m}}\) 时复杂度最优为 \(O(n \sqrt{m})\)

应用

歴史の研究

\(m\) 次询问区间内 \(\max a_i \times cnt_{a_i}\)

\(n, m \leq 10^5\)

模板,下给出参考代码。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

int bid[N];

struct Query {
	int l, r, id;
	
	inline bool operator < (const Query &b) const {
		return bid[l] == bid[b.l] ? r < b.r : l < b.l;
	}
} qry[N];

vector<int> vec;

ll ans[N];
int a[N], L[N], R[N], cnt[N], BFcnt[N];

ll result;
int n, m, block, tot;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void prework() {
	block = pow(n, 0.666) + 1;

	while (++tot) {
		L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
		fill(bid + L[tot], bid + R[tot] + 1, tot);

		if (R[tot] == n)
			break;
	}
}

inline ll BruteForce(int l, int r) {
	ll BFres = 0;
	
	for (int i = l; i <= r; ++i)
		BFres = max(BFres, 1ll * (++BFcnt[a[i]]) * vec[a[i]]);

	for (int i = l; i <= r; ++i)
		--BFcnt[a[i]];
	
	return BFres;
}

signed main() {
	n = read(), m = read();
	prework();
	
	for (int i = 1; i <= n; ++i)
		vec.emplace_back(a[i] = read());
	
	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());
	
	for (int i = 1; i <= n; ++i)
		a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();
	
	for (int i = 1; i <= m; ++i)
		qry[i].l = read(), qry[i].r = read(), qry[i].id = i;
	
	sort(qry + 1, qry + 1 + m);
	
	for (int i = 1, pos = 1; i <= tot; ++i) {
		memset(cnt, 0, sizeof(int) * vec.size());
		result = 0;
		
		for (int r = R[i], l = R[i] + 1; pos <= m && bid[qry[pos].l] == i; ++pos) {
			if (bid[qry[pos].r] == i) {
				ans[qry[pos].id] = BruteForce(qry[pos].l, qry[pos].r);
				continue;
			}
			
			while (r < qry[pos].r)
				++cnt[a[++r]], result = max(result, 1ll * cnt[a[r]] * vec[a[r]]);
			
			ll nowresult = result;
			
			while (l > qry[pos].l)
				++cnt[a[--l]], nowresult = max(nowresult, 1ll * cnt[a[l]] * vec[a[l]]);
			
			ans[qry[pos].id] = nowresult;
			
			while (l <= R[i])
				--cnt[a[l++]];
		}
	}
	
	for (int i = 1; i <= m; ++i)
		printf("%lld\n", ans[i]);
	
	return 0;
}

P5906 【模板】回滚莫队&不删除莫队

\(m\) 次询问区间中相同的数的最大出现下标差。

\(n, m \leq 2 \times 10^5\)

你说的对但这个是真模板题。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;

int bid[N];

struct Query {
	int l, r, id;
	
	inline bool operator < (const Query &b) const {
		return bid[l] == bid[b.l] ? r < b.r : l < b.l;
	}
} qry[N];

vector<int> vec;

int a[N], L[N], R[N], lpos[N], rpos[N], BFlpos[N], BFrpos[N], ans[N];

int n, m, block, tot;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void prework() {
	block = pow(n, 0.666) + 1;

	while (++tot) {
		L[tot] = R[tot - 1] + 1, R[tot] = min(block * tot, n);
		fill(bid + L[tot], bid + R[tot] + 1, tot);

		if (R[tot] == n)
			break;
	}
}

inline int BruteForce(int l, int r) {
	int result = 0;
	
	for (int i = l; i <= r; ++i) {
		BFlpos[a[i]] = min(BFlpos[a[i]], i), BFrpos[a[i]] = max(BFrpos[a[i]], i);
		result = max(result, BFrpos[a[i]] - BFlpos[a[i]]);
	}

	for (int i = l; i <= r; ++i)
		BFlpos[a[i]] = n + 1, BFrpos[a[i]] = 0;
	
	return result;
}

signed main() {
	n = read(), prework();
	
	for (int i = 1; i <= n; ++i)
		vec.emplace_back(a[i] = read());
	
	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());
	
	for (int i = 1; i <= n; ++i)
		a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

	m = read();
	
	for (int i = 1; i <= m; ++i)
		qry[i].l = read(), qry[i].r = read(), qry[i].id = i;
	
	sort(qry + 1, qry + 1 + m);
	fill(BFlpos, BFlpos + vec.size(), n + 1);
	fill(BFrpos, BFrpos + vec.size(), 0);
	
	for (int i = 1, pos = 1; i <= tot; ++i) {
		fill(lpos, lpos + vec.size(), n + 1);
		fill(rpos, rpos + vec.size(), 0);
		int result = 0;
		
		for (int r = R[i], l = R[i] + 1; pos <= m && bid[qry[pos].l] == i; ++pos) {
			if (bid[qry[pos].r] == i) {
				ans[qry[pos].id] = BruteForce(qry[pos].l, qry[pos].r);
				continue;
			}
			
			while (r < qry[pos].r) {
				++r;
				lpos[a[r]] = min(lpos[a[r]], r), rpos[a[r]] = max(rpos[a[r]], r);
				result = max(result, rpos[a[r]] - lpos[a[r]]);
			}
			
			int nowresult = result;
			
			while (l > qry[pos].l) {
				--l;
				BFlpos[a[l]] = min(BFlpos[a[l]], l), BFrpos[a[l]] = max(BFrpos[a[l]], l);
				nowresult = max(nowresult, max(rpos[a[l]], BFrpos[a[l]]) - min(lpos[a[l]], BFlpos[a[l]]));

			}			
			ans[qry[pos].id] = nowresult;

			while (l <= R[i])
				BFlpos[a[l]] = n + 1, BFrpos[a[l]] = 0, ++l;
		}
	}
	
	for (int i = 1; i <= m; ++i)
		printf("%d\n", ans[i]);
	
	return 0;
}

ZQUERY - Zero Query

\(m\) 次询问区间内区间和为 \(0\) 的最大区间长。

\(n, m \leq 5 \times 10^4, a_i = \pm 1\)

做完前缀和就和上题差不多了。

二维莫队

实现

每个状态有四个方向可以扩展,每次移动指针要操作一行或者一列的数,具体实现方式与普通的一维莫队类似。

取块长 \(B = n \times q^{-\frac{1}{4}}\) ,总时间复杂度为 \(O(n^2 \times q^{\frac{3}{4}})\)

应用

P1527 [国家集训队] 矩阵乘法

\(q\) 次询问 \(n \times n\) 矩阵中一个子矩阵的第 \(k\) 小数。

\(n \leq 500, q \leq 60000\)

先离散化,用莫队维护出矩形中出现的数,再值域分块即可。

这题好像二维莫队过不去,只有 \(60\) 分。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e2 + 7, M = 6e4 + 7;

int bid[N];

struct Query {
	int u, l, d, r, k, id;

	inline bool operator < (const Query &rhs) const {
		if (bid[u] == bid[rhs.u]) {
			if (bid[l] == bid[rhs.l]) {
				if (bid[r] == bid[rhs.r])
					return bid[r] & 1 ? d < rhs.d : d > rhs.d;
				else
					return bid[l] & 1 ? r < rhs.r : r > rhs.r;
			} else
				return bid[l] < bid[rhs.l];
		} else
			return bid[u] < bid[rhs.u];
	}
} qry[M];

vector<int> vec;

int a[N][N], cnt[N * N], s[N], ans[M];

int n, m, block, vblock;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void add1(int l, int r, int x) {
	for (int i = l; i <= r; ++i)
		++cnt[a[x][i]], ++s[a[x][i] / vblock];
}

inline void add2(int u, int d, int x) {
	for (int i = u; i <= d; ++i)
		++cnt[a[i][x]], ++s[a[i][x] / vblock];
}

inline void del1(int l, int r, int x) {
	for (int i = l; i <= r; ++i)
		--cnt[a[x][i]], --s[a[x][i] / vblock];
}

inline void del2(int u, int d, int x) {
	for (int i = u; i <= d; ++i)
		--cnt[a[i][x]], --s[a[i][x] / vblock];
}

inline int query(int k) {
	int cur = 0;

	while (k > s[cur])
		k -= s[cur++];

	cur *= vblock;

	while (k > cnt[cur])
		k -= cnt[cur++];

	return cur;
}

signed main() {
	n = read(), m = read(), block = n / pow(m, 0.25) / 3 + 1;

	for (int i = 1; i <= n; ++i)
		bid[i] = (i - 1) / block + 1;

	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			vec.emplace_back(a[i][j] = read());

	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());
	vblock = sqrt(vec.size());

	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			a[i][j] = lower_bound(vec.begin(), vec.end(), a[i][j]) - vec.begin();

	for (int i = 1; i <= m; ++i)
		qry[i].u = read(), qry[i].l = read(), qry[i].d = read(), qry[i].r = read(), qry[i].k = read(), qry[i].id = i;

	sort(qry + 1, qry + 1 + m);

	for (int i = 1, u = 1, l = 1, d = 0, r = 0; i <= m; ++i) {
		while (u > qry[i].u)
			add1(l, r, --u);

		while (l > qry[i].l)
			add2(u, d, --l);

		while (d < qry[i].d)
			add1(l, r, ++d);

		while (r < qry[i].r)
			add2(u, d, ++r);

		while (u < qry[i].u)
			del1(l, r, u++);

		while (l < qry[i].l)
			del2(u, d, l++);

		while (d > qry[i].d)
			del1(l, r, d--);

		while (r > qry[i].r)
			del2(u, d, r--);

		ans[qry[i].id] = vec[query(qry[i].k)];
	}

	for (int i = 1; i <= m; ++i)
		printf("%d\n", ans[i]);

	return 0;
}

莫队二次离线

适用范围:

  • 可以莫队。
  • 一个数对答案的贡献与区间中别的数有关,移动端点的更新时间不是 \(O(1)\)

考虑把莫队移动的这些端点也都离线下来预处理,从而进一步优化。

因为莫队时一次离线,最后处理时又一次离线,故称为莫队二次离线。

假设更新答案的时间复杂度为 \(O(k)\) ,则使用二次离线莫队可以将时间复杂度从 \(O(nk \sqrt{n})\) 降到 \(O(nk + n \sqrt{n})\)

实现

考虑端点移动对答案的影响,以 \([l, r] \rightarrow [l, r + k]\) 为例,设 \(x\) 对区间 \([l, r]\) 的贡献为 \(f(x, [l, r])\) ,则考虑求 \(\forall x \in [r + 1, r + k]\)\(\sum f(x, [l, x - 1])\) 的值。可以差分:

\[f(x, [l, x - 1]) = f(x, [1, x - 1]) - f(x, [1, l - 1]) \]

对于 \(f(x, [1, x - 1])\) 可以预处理得出,对于 \(f(x, [1, l - 1])\) 可以在每个 \(l - 1\) 的位置记录一下需要计算哪些数的贡献,打标记的时候可以只标记左右端点,最后一起处理。

注意最后处理时须保证查询复杂度为 \(O(1)\) ,时间复杂度才是 \(O(nk + n \sqrt{n})\)

应用

P4887 【模板】莫队二次离线(第十四分块(前体))

\(m\) 次查询,每次给出 \(l, r, k\) ,求满足 \(l \leq i < j \leq r, \operatorname{popcount}(a_i \oplus a_j) = k\) 的二元组 \((i,j)\) 的个数。

\(n, m \leq 10^5\)

模板,下给出参考代码。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7, V = 1 << 14 | 1;

struct Range {
	int l, r, id;

	inline Range() {}

	inline Range(int _l, int _r, int _id) : l(_l), r(_r), id(_id) {}
};

struct Query {
	int l, r, id, bid;
	
	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? r < rhs.r : l < rhs.l;
	}
} qry[N];

vector<Range> ask[N];
vector<int> AcceptedNumber;

ll res[N], ans[N];
int a[N], f[N], g[N];

int n, m, k, block;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

signed main() {
	n = read(), m = read(), k = read(), block = sqrt(n);
	
	for (int i = 1; i <= n; ++i)
		a[i] = read();
	
	for (int i = 0; i < V; ++i)
		if (__builtin_popcount(i) == k)
			AcceptedNumber.emplace_back(i);
	
	for (int i = 1; i <= n; ++i) {
		f[i] = g[a[i]]; // f[i] 表示 a[i] 对 [1, i - 1] 的贡献

		for (int x : AcceptedNumber)
			++g[x ^ a[i]];
	}
	
	for (int i = 1; i <= m; ++i)
		qry[i].l = read(), qry[i].r = read(), qry[i].id = i, qry[i].bid = qry[i].l / block;
	
	sort(qry + 1, qry + 1 + m);
	
	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		if (l > qry[i].l)
			ask[r].emplace_back(qry[i].l, l - 1, i);
		
		while (l > qry[i].l)
			res[i] -= f[--l];

		if (r < qry[i].r)
			ask[l - 1].emplace_back(r + 1, qry[i].r, -i);
		
		while (r < qry[i].r)
			res[i] += f[++r];
		
		if (l < qry[i].l)
			ask[r].emplace_back(l, qry[i].l - 1, -i);
		
		while (l < qry[i].l)
			res[i] += f[l++];
		
		if (r > qry[i].r)
			ask[l - 1].emplace_back(qry[i].r + 1, r, i);
		
		while (r > qry[i].r)
			res[i] -= f[r--];
	}
	
	memset(g, 0, sizeof(g));
	
	for (int i = 1; i <= n; ++i) {
		for (int x : AcceptedNumber)
			++g[x ^ a[i]];
		
		for (Range it : ask[i])
			for (int j = it.l; j <= it.r; ++j)
				if (it.id > 0)
					res[it.id] += g[a[j]] - (!k && j <= i);
				else
					res[-it.id] -= g[a[j]] - (!k && j <= i);
	}
	
	for (int i = 1; i <= m; ++i)
		res[i] += res[i - 1], ans[qry[i].id] += res[i];
	
	for (int i = 1; i <= m; ++i)
		printf("%lld\n", ans[i]);
	
	return 0;
}

[Ynoi2019 模拟赛] Yuno loves sqrt technology II

给你一个长为 \(n\) 的序列 \(a\)\(m\) 次询问区间的逆序对数。

\(n \leq 10^5\)

首先直接莫队是 \(O(n \sqrt{n} \log n)\) 的,然后二次离线就可以做到 \(O(n \sqrt{n} + n \log n)\) 了。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

struct Range {
    int l, r, id;

    inline Range() {}

    inline Range(int _l, int _r, int _id) : l(_l), r(_r), id(_id) {}
};

struct Query {
    int l, r, id, bid;
    
    inline bool operator < (const Query &rhs) const {
        return bid == rhs.bid ? r < rhs.r : l < rhs.l;
    }
} qry[N];

vector<Range> askl[N], askr[N];

ll pre[N], suf[N], res[N], ans[N];
int a[N];

int n, m, block;

template <class T = int>
inline T read() {
    char c = getchar();
    bool sign = (c == '-');
    
    while (c < '0' || c > '9')
        c = getchar(), sign |= (c == '-');
    
    T x = 0;
    
    while ('0' <= c && c <= '9')
        x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    
    return sign ? (~x + 1) : x;
}

namespace FK {
int Sum[N], sum[N], bel[N];

inline void prework() {
    for (int i = 0; i <= n; ++i)
        bel[i] = i / block + 1, Sum[i] = sum[i] = 0;
}

inline void update(int x) {
    for (int i = bel[x] + 1; i <= bel[n]; ++i)
        ++Sum[i];
    
    for (int pos = bel[x]; x <= n && bel[x] == pos; ++x)
        ++sum[x];
}

inline int query(int x) {
    return Sum[bel[x]] + sum[x];
}
} // namespace FK

signed main() {
    n = read(), m = read(), block = sqrt(n) + 1;
    
    vector<int> vec;
    
    for (int i = 1; i <= n; ++i)
        vec.emplace_back(a[i] = read());
    
    sort(vec.begin(), vec.end());
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
    
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin() + 1;
    
    FK::prework();
    
    for (int i = 1; i <= n; ++i)
        pre[i] = pre[i - 1] +  i - 1 - FK::query(a[i]), FK::update(a[i]);
    
    FK::prework();
    
    for (int i = n; i; --i)
        suf[i] = suf[i + 1] +  FK::query(a[i] - 1), FK::update(a[i]);
    
    for (int i = 1; i <= m; ++i)
        qry[i].l = read(), qry[i].r = read(), qry[i].id = i, qry[i].bid = qry[i].l / block;
    
    sort(qry + 1, qry + 1 + m);
    
    for (int i = 1, l = 1, r = 0; i <= m; ++i) {
    	res[i] += (suf[qry[i].l] - suf[l]);
    	res[i] += (pre[qry[i].r] - pre[r]);

        if (l > qry[i].l)
            askr[qry[i].r + 1].emplace_back(qry[i].l, l - 1, -i);
        
        if (r < qry[i].r)
            askl[l - 1].emplace_back(r + 1, qry[i].r, -i);
        
        if (l < qry[i].l)
            askr[qry[i].r + 1].emplace_back(l, qry[i].l - 1, i);
        
        if (r > qry[i].r)
            askl[l - 1].emplace_back(qry[i].r + 1, r, i);

        l = qry[i].l, r = qry[i].r;
    }
    
    FK::prework();
    
    for (int i = 1; i <= n; ++i) {
        FK::update(a[i]);
        
        for (Range it : askl[i])
            for (int j = it.l; j <= it.r; ++j)
                if (it.id > 0)
                    res[it.id] += i - FK::query(a[j]);
                else
                    res[-it.id] -= i - FK::query(a[j]);
    }
    
    FK::prework();
    
    for (int i = n; i; --i) {
        FK::update(a[i]);
        
        for (Range it : askr[i])
            for (int j = it.l; j <= it.r; ++j)
                if (it.id > 0)
                    res[it.id] += FK::query(a[j] - 1);
                else
                    res[-it.id] -= FK::query(a[j] - 1);
    }
    
    for (int i = 1; i <= m; ++i)
        res[i] += res[i - 1], ans[qry[i].id] = res[i];
    
    for (int i = 1; i <= m; ++i)
        printf("%lld\n", ans[i]);
    
    return 0;
}

P5501 [LnOI2019] 来者不拒,去者不追

\(m\) 次询问区间内所有数的价值和,一个数的价值定义为它在区间内的排名乘本身。

\(n, m \leq 5\times 10^5, a_i \leq 10^5\)

考虑加入 \(x\) 这个数的贡献:

  • 对于所有大于 \(x\) 的数 \(y\) ,贡献全部增加了 \(y\)
  • 对于 \(x\) 本身,贡献就是 \(x\times t\)\(t\)\([l,r]\) 中比 \(x\) 小的数加 \(1\)

于是莫队二次离线时维护大于 \(x\) 的数的总和和小于 \(x\) 的数的数量即可。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7, V = 1e5 + 7;

struct Range {
    int l, r, id;

    inline Range() {}

    inline Range(int _l, int _r, int _id) : l(_l), r(_r), id(_id) {}
};

struct Query {
	int l, r, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? r < rhs.r : l < rhs.l;
	}
} qry[N];

vector<Range> ask[N];

ll s[N], pre[N], res[N], ans[N];
int a[N];

int n, m, block;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

namespace FK {
const int block = sqrt(V);

ll Sum[V], sum[V];
int Cnt[V], cnt[V];

ll total;

inline void clear() {
	memset(Sum, 0, sizeof(Sum));
	memset(sum, 0, sizeof(sum));
	memset(Cnt, 0, sizeof(Cnt));
	memset(cnt, 0, sizeof(cnt));
	total = 0;
}

inline void update(int x) {
	total += x;

    for (int i = x / block; i <= (V - 1) / block; ++i)
        Sum[i] += x, ++Cnt[i];

    for (int i = x; i < V && i / block == x / block; ++i)
    	sum[i] += x, ++cnt[i];
}

inline ll querysum(int x) {
    return (x / block ? Sum[x / block - 1] : 0) + sum[x];
}

inline int querycnt(int x) {
    return (x / block ? Cnt[x / block - 1] : 0) + cnt[x];
}

inline ll query(int x) {
	return total - querysum(x) + 1ll * x * querycnt(x - 1);
}
} // namespace FK

signed main() {
	n = read(), m = read(), block = sqrt(n) + 1;

	for (int i = 1; i <= n; ++i)
		s[i] = s[i - 1] + (a[i] = read());

	FK::clear();

	for (int i = 1; i <= n; ++i)
		pre[i] = pre[i - 1] + FK::query(a[i]), FK::update(a[i]);

	for (int i = 1; i <= m; ++i)
		qry[i].l = read(), qry[i].r = read(), qry[i].id = i, qry[i].bid = qry[i].l / block;

	sort(qry + 1, qry + 1 + m);

	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		if (l > qry[i].l) {	
			ask[r].emplace_back(qry[i].l, l - 1, i);
			res[i] -= pre[l - 1] - pre[qry[i].l - 1], l = qry[i].l;
		}

		if (r < qry[i].r) {
			ask[l - 1].emplace_back(r + 1, qry[i].r, -i);
			res[i] += pre[qry[i].r] - pre[r], r = qry[i].r;
		}
		
		if (l < qry[i].l) {
			ask[r].emplace_back(l, qry[i].l - 1, -i);
			res[i] += pre[qry[i].l - 1] - pre[l - 1], l = qry[i].l;
		}
		
		if (r > qry[i].r) {
			ask[l - 1].emplace_back(qry[i].r + 1, r, i);
			res[i] -= pre[r] - pre[qry[i].r], r = qry[i].r;
		}
	}

    FK::clear();

    for (int i = 1; i <= n; ++i) {
    	FK::update(a[i]);

    	for (Range it : ask[i])
    		for (int j = it.l; j <= it.r; ++j)
    			if (it.id > 0)
    				res[it.id] += FK::query(a[j]);
    			else
    				res[-it.id] -= FK::query(a[j]);
    }

	for (int i = 1; i <= m; ++i)
		res[i] += res[i - 1], ans[qry[i].id] = res[i] + s[qry[i].r] - s[qry[i].l - 1];

	for (int i = 1; i <= m; ++i)
		printf("%lld\n", ans[i]);

	return 0;
}

莫队配合 bitset

P4688 [Ynoi2016] 掉进兔子洞

\(m\) 次询问,每次询问三个区间,把三个区间中同时出现的数一个个删掉,求最后三个区间剩下的数的个数和,询问独立。

\(n, m \leq 10^5, a_i \leq 10^9\)

答案为 \(\sum_{i = 1}^3 (r_i - l_i + 1) - 3 \times k\),其中 \(k\) 为三段区间内共有的数的个数,问题转化为求 \(k\)

考虑用莫队维护查询的区间内每个数的个数。对于一次询问,我们将其拆成传统莫队的三个询问:\((l_1, r_1), (l_2, r_2), (l_3, r_3)\) ,分别进行处理。我们要得到 \(k\),需要得到这三个询问中有哪些数、分别几个,然后取它们的交集即可。

注意一个数可能重复出现多次。我们考虑先将初始 \(a\) 数组进行离散化,只记录 \(a\) 数组每一个值对应排序好的数组的编号即可。这样,若干个相同的数便会留出若干个空着的编号,它们代表同一个值。然后使用这个离散化的对应关系用 bitset 存即可。

但是 \(10^5\) 的范围 bitset 存不下,可以把输入的查询分为若干组,每组 \(2\times 10^4\) 个数,对于每一组分别进行处理即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7, M = 2e4 + 7;

struct Query {
	int l, r, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
	}
} qry[N];

bitset<N> res[M], result;

int a[N], cnt[N], ans[M];

int n, m, block;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void add(int x) {
	result.set(a[x] + (cnt[a[x]]++));
}

inline void del(int x) {
	result.reset(a[x] + (--cnt[a[x]]));
}

signed main() {
	n = read(), m = read(), block = sqrt(n);
	vector<int> vec;

	for (int i = 1; i <= n; ++i)
		vec.emplace_back(a[i] = read());

	sort(vec.begin(), vec.end());

	for (int i = 1; i <= n; ++i)
		a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin();

	for (int task = 1; task <= 5; ++task) {
		int q = min(m, 20000), qcnt = 0;

		for (int i = 1; i <= q; ++i) {
			ans[i] = 0, res[i].set();

			for (int j = 1; j <= 3; ++j) {
				qry[++qcnt].l = read(), qry[qcnt].r = read();
				qry[qcnt].id = i, qry[qcnt].bid = qry[qcnt].l / block;
				ans[i] += qry[qcnt].r - qry[qcnt].l + 1;
			}
		}

		sort(qry + 1, qry + 1 + qcnt);
		memset(cnt, 0, sizeof(cnt));
		result.reset();

		for (int i = 1, l = 1, r = 0; i <= qcnt; ++i) {
			while (l > qry[i].l)
				add(--l);

			while (r < qry[i].r)
				add(++r);

			while (l < qry[i].l)
				del(l++);

			while (r > qry[i].r)
				del(r--);

			res[qry[i].id] &= result;
		}

		for (int i = 1; i <= q; ++i)
			printf("%d\n", ans[i] - (int)res[i].count() * 3);

		m = max(m - 20000, 0);
	}

	return 0;
}

P5355 [Ynoi2017] 由乃的玉米田

\(m\) 次询问,每次询问区间内是否能找出两个数使得它们的差/和/积/商为 \(x\)

\(n, m, a_i \leq 10^5\)

加减维护一个 bitset 就好了,单次询问复杂度为 \(O(\dfrac{n}{\omega})\)

积直接暴力枚举因数,单次询问复杂度为 \(O(\sqrt{n})\)

对于商,分类讨论:

  • 如果 \(x \geq \sqrt{n}\) ,那么可以暴力枚举商,然后判断有没有出现即可。因为这个商 \(\leq \sqrt{n}\) ,所以复杂度是正确的。
  • 如果 \(x < \sqrt{n}\) ,可以预处理出 \(x \in [1, \sqrt{n})\) 的答案。对于每个 \(x\) 遍历一遍序列,找出每个 \(1 \leq i \leq n\) 的离 \(i\) 最近且 \(\leq i\)\(tp_i\) ,满足 \(a_i\)\(a_{tp_i}\) 的商为 \(x\) ,于是每个询问的答案为 \(l \leq tp_r\) 。这一部分的时间复杂度为 \(O(n \sqrt{n})\)

总时间复杂度为 \(O(\dfrac{n^2}{\omega} + n \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;

struct Query {
	int op, l, r, k, id, bid;

	inline bool operator < (const Query &rhs) const {
		return bid == rhs.bid ? (bid & 1 ? r > rhs.r : r < rhs.r) : l < rhs.l;
	}
} qry[N];

struct Ask {
	int l, r, id;
};

bitset<N> result1, result2; // i in result1, N - i in result2
vector<Ask> ask[N];

int a[N], cnt[N], pre[N], tp[N];
bool ans[N];

int n, m, block, cntq;

template <class T = int>
inline T read() {
	char c = getchar();
	bool sign = (c == '-');
	
	while (c < '0' || c > '9')
		c = getchar(), sign |= (c == '-');
	
	T x = 0;
	
	while ('0' <= c && c <= '9')
		x = (x << 1) + (x << 3) + (c & 15), c = getchar();
	
	return sign ? (~x + 1) : x;
}

inline void add(int x) {
	if (!cnt[a[x]])
		result1.set(a[x]), result2.set(N - a[x]);

	++cnt[a[x]];
}

inline void del(int x) {
	--cnt[a[x]];

	if (!cnt[a[x]])
		result1.reset(a[x]), result2.reset(N - a[x]);
}

signed main() {
	n = read(), m = read(), block = sqrt(n) + 1;

	for (int i = 1; i <= n; ++i)
		a[i] = read();

	for (int i = 1; i <= m; ++i) {
		int op = read(), l = read(), r = read(), k = read();

		if (op == 4 && k <= block)
			ask[k].emplace_back((Ask) {l, r, i});
		else
			qry[++cntq] = (Query){op, l, r, k, i, l / block};
	}

	sort(qry + 1, qry + 1 + cntq);

	for (int i = 1, l = 1, r = 0; i <= cntq; ++i) {
		while (l > qry[i].l)
			add(--l);

		while (r < qry[i].r)
			add(++r);

		while (l < qry[i].l)
			del(l++);

		while (r > qry[i].r)
			del(r--);

		if (qry[i].op == 1)
			ans[qry[i].id] = (result1 & (result1 << qry[i].k)).any();
		else if (qry[i].op == 2) {
			ans[qry[i].id] = (result1 & (result2 >> (N - qry[i].k))).any();
		} else if (qry[i].op == 3) {
			for (int j = 1; j * j <= qry[i].k; ++j)
				if (!(qry[i].k % j) && result1.test(j) && result1.test(qry[i].k / j)) {
					ans[qry[i].id] = true;
					break;
				}
		} else {
			for (int j = 1; j * qry[i].k < N; ++j)
				if (result1.test(j) && result1.test(j * qry[i].k)) {
					ans[qry[i].id] = true;
					break;
				}
		}
	}

	for (int i = 1; i <= block; ++i) {
		if (ask[i].empty())
			continue;

		memset(pre, 0, sizeof(pre));
		memset(tp, 0, sizeof(tp));

		for (int j = 1, pos = 0; j <= n; ++j) {
			pre[a[j]] = j;

			if (a[j] * i < N)
				pos = max(pos, pre[a[j] * i]);

			if (!(a[j] % i))
				pos = max(pos, pre[a[j] / i]);

			tp[j] = pos;
		}

		for (Ask it : ask[i])
			ans[it.id] = (it.l <= tp[it.r]);
	}

	for (int i = 1; i <= m; ++i)
		puts(ans[i] ? "yuno" : "yumi");

	return 0;
}

P5313 [Ynoi2011] WBLT

\(m\) 次询问区间内元素组成最长的首项 \(< b\) 公差为 \(b\)\(b\) 每次询问给出)的等差数列的长度。

\(n, m, a_i \leq 10^5\)

\(b > B\) 时,用莫队提取出一段区间中代表数字出现情况的 bitset ,然后把这个大 bitset\(0\) 开始分裂成一些长度为 \(b\) 的小 bitset 。显然,如果将分裂出来的这些 bitset 全部与起来,第一次全为 \(0\) 时的小 bitset 的下标即为本次询问的答案。

\(b \leq B\) 时,考虑对每个 \(b\) 的剩余类分别处理,原问题可转化为在长为 \(nB\) 的序列上,询问 \(qB\) 次区间 \(\operatorname{mex}\) 。线段树上二分可以做到 \(O(nB\log n)\)

posted @ 2024-07-20 15:54  我是浣辰啦  阅读(3)  评论(0编辑  收藏  举报