莫队
区间问题的一种暴力方法就是从一个区间到另一个区间时,把左右端点分别一个点一个点地挪过去,但是这样显然是 \(\text{O}(n^2)\) 的。莫队就是通过离线这些区间然后用特别的排序方式使得这个暴力的复杂度降至根号级别。
建议边看边画图,便于理解。
普通莫队
把数组分成 \(\sqrt{n}\) 段,像分块一样。此时把每个询问的区间按 左端点所在块 排序,若在同一块,则按右端点从左到右排序。
那么此时,对于每一块的询问,左端点每次最多移动 \(\sqrt{n}\) 次,右端点总共最多移动 \(n\) 次,总复杂为 \(\text{O}(n\sqrt{n})\)。
同时,还可以进一步小优化:对于奇数段,右端点从左到右,对于偶数段,右端点从右到左。
例题 P1494
#include <bits/stdc++.h>
//#define int long long
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <int, pii> piii;
const double PI = acos (-1);
const double eps = 1e-10;
const int N = 5e4 + 10, M = 2e5 + 10;
//const int mod = 1e9 + 7;
//const int mod = 998244353;
int a[N];
struct node
{
int l, r, pos, id;
} q[N];
bool cmp(node x, node y)
{
if (x.pos != y.pos) return x.pos < y.pos;
else if (x.pos & 1) return x.r < y.r;
else return x.r > y.r;
}
ll res, cnt[N];
pii ans[N];
void add(int x, int op)
{
res -= cnt[x] * cnt[x];
cnt[x] += op;
res += cnt[x] * cnt[x];
}
signed main()
{
cin.tie (0);
ios :: sync_with_stdio (false);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
int len = sqrt (n);
for (int i = 1; i <= m; i++)
{
cin >> q[i].l >> q[i].r;
q[i].pos = (q[i].l - 1) / len + 1;
q[i].id = i;
}
sort (q + 1, q + m + 1, cmp);
// for (int i = q[1].l; i <= q[1].r; i++)
// add (a[i], 1);
// int l = q[1].l, r = q[1].r;
// if (l == r) ans[q[1].id] = {0, 1};
// else ans[q[1].id] = {res - (r - l + 1), (r - l + 1) * (r - l)};
int l = 1, r = 0;
for (int i = 1; i <= m; i++)
{
while (r > q[i].r) add (a[r--], -1);
while (r < q[i].r) add (a[++r], 1);
while (l > q[i].l) add (a[--l], 1);
while (l < q[i].l) add (a[l++], -1);
ll len = r - l + 1;
if (l == r) ans[q[i].id] = {0, 1};
else ans[q[i].id] = {res - len, len * (len - 1)};
}
for (int i = 1; i <= m; i++)
{
ll x = ans[i].x, y = ans[i].y;
ll gcd = __gcd (x, y);
x /= gcd, y /= gcd;
cout << x << "/" << y << "\n";
}
return 0;
}
带修莫队
由于带修,我们不能像普通莫队那样的顺序处理询问了,但是我们换一种排序方式即可。
先按左端点所在块从左到右排序,这个没有问题,然后若在同一块,按 右端点所在块从左到右 排序,若还在同一块,按 区间版本号从小到大 排序。
版本号就是这个询问前面有多少次修改。
注意除了左右端点要移动以外,版本号也要一个一个变,即暴力修改。
时间复杂度我不会证啊,注意块长。
例题 P1903
#include <bits/stdc++.h>
//#define int long long
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <int, pii> piii;
const double PI = acos (-1);
const double eps = 1e-10;
const int N = 2e5 + 10, M = 2e5 + 10;
//const int mod = 1e9 + 7;
//const int mod = 998244353;
struct node
{
int l, r, pl, pr;
int t, id;
} q[N];
bool cmp(node x, node y)
{
if (x.pl != y.pl) return x.pl < y.pl;
else if (x.pr != y.pr) return x.pr < y.pr;
else return x.t < y.t;
}
pair <int, int> t[N];
int qid, tid, a[N], cnt[5 * N], ans[N], res;
void add(int x, int op)
{
cnt[x] += op;
if (cnt[x] == 0 && op == -1) res--;
if (cnt[x] == 1 && op == 1) res++;
}
signed main()
{
cin.tie (0);
ios :: sync_with_stdio (false);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
int len = pow (n, 0.6666); //注意块长注意块长注意块长
for (int i = 1; i <= m; i++) //离线修改和询问
{
char op;
int x, y;
cin >> op >> x >> y;
if (op == 'Q')
q[++qid] = {x, y, (x - 1) / len + 1, (y - 1) / len + 1, tid, qid};
else t[++tid] = {x, y};
}
sort (q + 1, q + qid + 1, cmp);
int l = 1, r = 0, now = 0;
for (int i = 1; i <= qid; i++)
{
while (r > q[i].r) add (a[r--], -1);
while (r < q[i].r) add (a[++r], 1);
while (l > q[i].l) add (a[--l], 1);
while (l < q[i].l) add (a[l++], -1);
while (now != q[i].t) //版本更新
{
if (now < q[i].t) now++;
int &x = t[now].x, &y = t[now].y;
if (l <= x && x <= r)
add (a[x], -1), add (y, 1);
swap (a[x], y);//小技巧,下次到这个点就是改回来,所以可以直接对修改进行改变
if (now > q[i].t) now--;
}
ans[q[i].id] = res;
}
for (int i = 1; i <= qid; i++)
cout << ans[i] << "\n";
return 0;
}

浙公网安备 33010602011771号