浅谈莫队2
莫队
二次离线莫队
AcWing 2535. 二次离线莫队
本题有若干个询问,每个询问都要求出某个区间中异或和在二进制表示中有 个 的数对个数。
我们规定,如果某两个数的异或和在二进制表示中有 个 ,我们就称这两个数是配对的,因此每个询问就变成了求某个区间中有多少数对是配对的。
本题需要用到二次离线莫队来做,而二次离线莫队就是一共需要离线两次来做,在做莫队时,我们每次都会对一段区间去查询一个数,然后我们都会去对两个端点进行移动,然后在新的维护区间中去求我们的询问。
而对于二次离线莫队,就是当我们每次更新完维护区间之后,对于区间的询问很难算,所以我们需要在每次更新完维护区间之后,再把当前询问单独拎出来再重新离线求当前询问的值。二次离线算法思维难度一般不高,但是代码实现中的细节非常多,且每道题都需要重新思考,可以说是非常恶心。
要想使用莫队算法来求,那么每次我们都需要从上一个询问区间 的信息快速得到当前询问区间 的信息。以右端点 为例,当 右移后,我们需要将 加入到维护区间中,那么我们就需要考虑将 加入到维护区间中后,维护区间的信息该怎么去维护。当我们将 加入后,需要求一下它对于配对的数量有什么样的影响,显然配对的数量只会增加,至于增加的数量,就是 中和 配对的数的个数,这一步可以用前缀和来求。
设 表示 中有多少个数和 配对,此时对于 中和 配对的数的个数就是 。接下来就是要求 和 ,对于 ,其实是问 中有多少个数和 配对,可以发现要询问的数就是区间的后一个数,而 则没有这么好的性质, 和 之间的距离是非常随机的,毫无规律可循,因此 和 其实是两类询问,分两种情况来考虑。
首先对于 ,由于这一部分是非常有规律的,所以可以提前预处理,设 表示 中与 配对的数个数,而 显然就是 ,因此我们需要快速的预处理出 ,可以用一个 表示前 个数中有多少个数与 配对。当我们把 预处理出来,则 。
因此我们现在就是需要求出 阶段的 ,假设当前 表示前 个数中有多少个数与 配对,这里我们可以先预处理出 中所有有 个 的数 ,我们想从前 个数的 变成前 个数的 ,相当于是加入了一个新的数 ,此时我们只要找出所有和 配对的 ,令 ,最终就能得到前 个数的 ,而我们要找的所有 则必须满足 ,这个条件等价于 ,因此我们可以枚举所有不同的 ,通过 计算出所有的 ,因为 不同,所以得出的 也不同。
综上所述,对于前 个数的 ,我们枚举所有 ,令 ,最终就能得到前 个数的 ,然后再用 计算 即可。这样我们就能用 作为辅助递推得出所有的 。这一部分预处理一共只需要做一次,由于 比较小, 最多只有三千多个,因此预处理的计算量最多只有三千多万。
接下来我们需要想办法求出 , 通过我们刚才的分析,我们可以在做莫队的过程中在线求出来,但是 并不能马上求出来,因此我们只能先将所有要求 的问题先找出来,然后我们再离线把所有要求的 求出来,最后我们才能求出 。
要求 ,其实就是求 中有多少个数是和 配对的,以此类推,在从 移动到 的过程中,我们会将 都加入到维护区间中,因此对于 ,都要求出 中和 配对的数的个数,可以发现这些问题都是问某个固定前缀中,某个区间的每个数和这个前缀中有多少个数配对。我们可以将这些询问全部找出来,然后按照从前往后的顺序计算所有询问,我们先算一下所有前缀是 的询问,再算一下所有前缀是 的询问,以此类推,直到我们算完所有前缀是 的询问后,我们就将所有的询问都处理完了。
由于我们是从前往后做所有询问,因此每一次前缀中只会增加一个数,因此这里我们同样可以用一个 数组来作为辅助,表示的内容和上面相同,同样表示前 个数中与 配对的数的个数,因此对于 ,我们想求的 中和 配对的数的个数恰好就是 ,直接按照上面更新 的思路依次往后求即可。而这一部分的询问数量应该取决于两个指针移动的次数,这个在基础莫队中就已经证明过是 级别的,因此我们就能用一个 的离线做法求出所有的 ,然后就能把这部分在莫队中无法解决的问题统一计算出来。
到此我们就能将前一个询问到当前询问的增量 求出来,但是这并不是当前询问的答案,如果我们想求某一个询问的结果的话,还需要将前面求出来的所有增量累加成前缀和才是最终答案。
注意,上面我们推导了 向右移动到 这一种情况,实际上两个指针一共有四种情况,而其他三种情况都按照上面同样的形式去分析即可,代码实现时需要根据每种情况的区别做一些更细致的处理,这里就不过多赘述,直接体现在代码中。
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 5;
int n, m, k, len;
int w[N], g[N], f[N];
int ans[N];
struct node
{
int id, l, r, t;
int res;
} q[N];
vector<node> range[N];
int get(int i){return i / len;}
bool cmp(node a, node b)
{
int l = get(a.l), r = get(b.l);
if (l != r) return l < r;
return a.r < b.r;
}
bool count(int i)
{
int res = 0;
for (rint j = 0; j < 14; j++)
if (i >> j & 1)
res++;
return res == k;
}
signed main()
{
cin >> n >> m >> k;
for (rint i = 1; i <= n; i++)
{
cin >> w[i];
}
for (rint i = 1; i <= m; i++)
{
int l, r;
cin >> l >> r;
q[i] = {i, l, r};
}
vector<int> nums;
for (rint i = 0; i < (1 << 14); i++)
{
if (count(i))
{
nums.push_back(i);
}
}
for (rint i = 1; i <= n; i++)
{
for (auto y : nums) g[w[i] ^ y]++;
f[i] = g[w[i + 1]];
}
len = sqrt(n);
sort(q + 1, q + m + 1, cmp);
for (rint i = 1, L = 1, R = 0; i <= m; i++)
{
int l = q[i].l, r = q[i].r;
if (R < r) range[L - 1].push_back({i, R + 1, r, -1});
while (R < r) q[i].res += f[R++];
if (R > r) range[L - 1].push_back({i, r + 1, R, 1});
while (R > r) q[i].res -= f[--R];
if (L < l) range[R].push_back({i, L, l - 1, -1});
while (L < l) q[i].res += f[L - 1] + !k, L++;
if (L > l) range[R].push_back({i, l, L - 1, 1});
while (L > l) q[i].res -= f[L - 2] + !k, L--;
}
memset(g, 0, sizeof g);
for (rint i = 1; i <= n; i++)
{
for (auto y : nums) g[w[i] ^ y]++;
for (auto &rg : range[i])
{
int id = rg.id, l = rg.l, r = rg.r, t = rg.t;
for (rint x = l; x <= r; x++)
{
q[id].res += t * g[w[x]];
}
}
}
for (rint i = 2; i <= m; i++)
{
q[i].res += q[i - 1].res;
}
for (rint i = 1; i <= m; i++)
{
ans[q[i].id] = q[i].res;
}
for (rint i = 1; i <= m; i++)
{
cout << ans[i] << endl;
}
return 0;
}
树上莫队
SP10707
先将整棵树的欧拉序求出来,记录每个点第一次出现的位置 和最后一次出现的位置 ,然后观察树中的路径 可以发现两种情况:
- 如果路径是一条从上往下的直链,则其所有点对应欧拉序中 到 中出现一次的点
- 否则其所有点对应欧拉序中 到 中出现一次的点加上
理解一下会发现的确这样,然后问题就转化为普通莫队问题了
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
#define queue queue__
using namespace std;
const int N = 2e6 + 5;
const int M = 1e7 + 5;
int n, m, len;
int h[N], e[M], ne[M], idx;
int w[N], seq[N], first[N], last[N], top;
int queue[N], dep[N], fa[N][25], cnt[N];
int ans[N];
bool st[N];
vector<int> nums;
struct node
{
int id, l, r, p;
} q[N];
void add(int a, int b)
{
e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}
int get(int i){return i / len;}
bool cmp(node a, node b)
{
int l = get(a.l), r = get(b.l);
if (l != r) return l < r;
return a.r < b.r;
}
void dfs(int x, int father)
{
seq[++top] = x;
first[x] = top;
for (rint i = h[x]; i; i = ne[i])
{
int y = e[i];
if (y != father)
{
dfs(y, x);
}
}
seq[++top] = x;
last[x] = top;
}
void bfs(int s)
{
memset(dep, -1, sizeof dep);
int hh = 0, tt = 0;
queue[0] = s, dep[s] = 0;
while (hh <= tt)
{
int x = queue[hh++];
for (rint i = h[x]; i; i = ne[i])
{
int y = e[i];
if (dep[y] == -1)
{
dep[y] = dep[x] + 1;
fa[y][0] = x;
queue[++tt] = y;
for (rint j = 1; j <= 20; j++)
{
fa[y][j] = fa[fa[y][j - 1]][j - 1];
}
}
}
}
}
int lca(int a, int b)
{
if (dep[a] < dep[b]) swap(a, b);
for (rint i = 20; i >= 0; i--)
if (dep[fa[a][i]] >= dep[b])
a = fa[a][i];
if (a == b) return a;
for (rint i = 20; i >= 0; i--)
if (fa[a][i] != fa[b][i])
a = fa[a][i], b = fa[b][i];
return fa[a][0];
}
void change(int x, int &res)
{
st[x] ^= 1;
if (st[x] == 0)
{
cnt[w[x]]--;
if (!cnt[w[x]]) res--;
}
else
{
if (!cnt[w[x]]) res++;
cnt[w[x]]++;
}
}
signed main()
{
cin >> n >> m;
for (rint i = 1; i <= n; i++)
{
cin >> w[i];
nums.push_back(w[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for (rint i = 1; i <= n; i++)
{
w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();
}
for (rint i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
bfs(1);
dfs(1, 1);
len = sqrt(top);
for (rint i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
if (first[x] > first[y]) swap(x, y);
int p = lca(x, y);
if (x == p) q[i] = {i, first[x], first[y]};
else q[i] = {i, last[x], first[y], p};
}
sort(q + 1, q + m + 1, cmp);
for (rint k = 1, i = 1, j = 0, res = 0; k <= m; k++)
{
int id = q[k].id, l = q[k].l, r = q[k].r, p = q[k].p;
while (i < l) change(seq[i++], res);
while (i > l) change(seq[--i], res);
while (j < r) change(seq[++j], res);
while (j > r) change(seq[j--], res);
if (p) change(p, res);
ans[id] = res;
if (p) change(p, res);
}
for (rint i = 1; i <= m; i++)
{
cout << ans[i] << endl;
}
return 0;
}
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/17938662
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步