莫队
普通莫队
P2709 小B的询问 /【模板】莫队
题意:
小 B 有一个长为 \(n\) 的整数序列 \(a\),值域为 \([1,k]\)。
他一共有 \(m\) 个询问,每个询问给定一个区间 \([l,r]\),求:
\(\sum\limits_{i=1}^k c_i^2\)
其中 \(c_i\) 表示数字 \(i\) 在 \([l,r]\) 中的出现次数。
思路:
莫队板子
-
将每个询问的 \(l\) 和 \(r\) 存下来,方便离线处理
-
将每个询问 \(l\) 所在的块编号作为第一关键字,将 \(r\) 作为第二关键字排序
为什么是按左端点所在块的编号? 因为这样可以保证 \(l\) 和 \(r\) 指针没有较大的移动
-
对于每个询问,我们移动 \(L\) 和 \(R\) 指针,移动的过程中记录当前答案。当 \(L = l, R = r\) 时,记录答案,并处理下一个询问
-
当 \(l\) 指针向右移动时,删除最右端的数,减少答案
-
当 \(l\) 指针向左移动时,新加入数,增加答案
-
当 \(r\) 指针向右移动时,新加入数,增加答案
-
当 \(r\) 指针向左移动时,删除最左端的数,减少答案
-
-
按输入顺序输出得到的结果
至于 \(c_i ^ 2\) 怎么处理,使用我们小学就学过的平方差公式,增加的时候增加 \(2 \times c_i + 1\),减少的时候减少 \(2 \times c_i - 1\) 即可。
由于每个块的大小不超过 \(O(\sqrt{n})\),所以总时间复杂度为 \(O(n\sqrt{n})\)
code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
struct node
{
int l, r, id;
} mo[50005];
int a[50005], b[50005], B, n, k, q, l = 1, r = 1, ll, rr;
long long c, ans[500005];
bool cmp(node a, node b)
{
if ((a.l) / B != (b.l) / B)
return a.l / B < b.l / B;
return a.r < b.r;
}
void add(int x)
{
c += 2 * b[x] + 1;
b[x]++;
}
void del(int x)
{
c -= 2 * b[x] - 1;
b[x]--;
}
signed main()
{
cin >> n >> q >> k;
B = sqrt(n);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i <= q; i++)
{
cin >> mo[i].l >> mo[i].r;
mo[i].id = i;
}
sort(mo + 1, mo + q + 1, cmp);
for (int i = 1; i <= q; i++)
{
l = mo[i].l, r = mo[i].r;
while (ll > l)
{
ll--;
add(a[ll]);
}
while (rr < r)
{
rr++;
add(a[rr]);
}
while (ll < l)
{
del(a[ll]);
ll++;
}
while (rr > r)
{
del(a[rr]);
rr--;
}
ans[mo[i].id] = c;
}
for (int i = 1; i <= q; i++)
{
cout << ans[i] - 1 << endl;
}
}
P4462 [CQOI2018] 异或序列
题意:
已知一个长度为 \(n\) 的整数数列 \(a_1,a_2,\dots,a_n\),给定查询参数 \(l,r\),问在 \(a_l,a_{l+1},\dots,a_r\) 区间内,有多少子区间满足异或和等于 \(k\)。也就是说,对于所有的 \(x,y (l \leq x \leq y \leq r)\),能够满足 \(a_x \oplus a_{x+1} \oplus \dots \oplus a_y = k\) 的 \(x,y\) 有多少组。
思路:
考虑维护一个前缀异或和 \(sum_i\),这样就可以在 \(O(1)\) 的时间复杂度下求出一段区间的异或和。
那么题目就转化为了区间有多少组数 \((l, r)\) 满足 \(sum_{l - 1} ⊕ sum_r = k\) 。稍微变一下就可以得到 \(sum_r ⊕ k = sum_{l - 1}\)。
所以我们需要维护的东西就是 \(sum_{l - 1}\),由于这一道题值域不大,所以直接开个桶 \(cnt\) 存当前区间的前缀异或和,每次找 \(cnt_{sum_r} ⊕ k = sum_{l - 1}\) 的值就可以了。
这样可以做到单次移动端点 \(O(1)\),总时间复杂度 \(O(n\sqrt{n})\) 。
坑点:
-
开
long long -
add中先更新ans,再更新桶,而del中先更新桶,再更新ans
-
数组需要开到
2e5 -
\(l\) 初始化为 \(0\),因为查询区间是 \([0, n]\)
code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int QWQ = 5e5 + 5;
int a[QWQ], pos[QWQ], L[QWQ], R[QWQ], n, m, k;
int block, num, cnt[QWQ], sum[QWQ], ans, q[QWQ];
struct node
{
int l, r, id;
} w[QWQ];
bool cmp(node a, node b)
{
if (pos[a.l] == pos[b.l])
{
return a.r < b.r;
}
return a.l < b.l;
}
void init()
{
block = sqrt(n);
num = n / block;
for (int i = 1; i <= n; i++)
{
pos[i] = i / block + 1;
}
for (int i = 1; i <= num; i++)
{
L[i] = block * (i - 1) + 1;
R[i] = block * i;
}
R[num] = n;
}
void add(int x)
{
ans += cnt[a[x] ^ k];
cnt[a[x]]++;
}
void del(int x)
{
cnt[a[x]]--;
ans -= cnt[a[x] ^ k];
}
signed main()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
a[i] ^= a[i - 1];
}
init();
for (int i = 1; i <= m; i++)
{
cin >> w[i].l >> w[i].r;
w[i].id = i;
}
sort(w + 1, w + m + 1, cmp);
int l = 0, r = 0;
cnt[0] = 1;
for (int i = 1; i <= m; i++)
{
w[i].l = w[i].l - 1;
while (l < w[i].l)
{
del(l ++);
}
while (l > w[i].l)
{
add(-- l);
}
while (r < w[i].r)
{
add(++r);
}
while (r > w[i].r)
{
del(r--);
}
q[w[i].id] = ans;
}
for (int i = 1; i <= m; i++)
{
cout << q[i] << endl;
}
}

浙公网安备 33010602011771号