学习笔记:莫队算法
暴力,因分块而优雅,因莫队而芳华。
形式
对于序列上的区间询问问题,如果从 \([l,r]\) 的答案能够 \(O(1)\) 扩展到 \([l,r+1]\),\([l,r-1]\),\([l-1,r]\),\([l+1,r]\)(即与 \([l,r]\) 相邻的区间)的答案,那么可以在 \(O(n\sqrt n)\) 的复杂度内求出所有询问的答案。
实现
离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。
排序方法
对于区间 \([l,r]\), 以 \(l\) 所在块的编号为第一关键字,\(r\) 为第二关键字从小到大排序。
复杂度分析
待补
例题 P2709 小B的询问
传送门:例题
莫队例题,对于询问区间,考虑能否 \(O(1)\) 扩展到相邻区间,答案是显然可以。某一个颜色的加减不影响到其它颜色,对于一种颜色 \(col_i\),多一个对答案贡献增加 \(2\times col_i + 1\),少一个答案贡献减少 \(2\times col_i - 1\)。
使用莫队即可,时间复杂度 \(O(n\sqrt n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 400000 + 10;
struct node
{
int l,r,id;
} q[N];
int n,m,k,res,block,l = 1,r;
int ans[N],spl[N],L[N],R[N],cnt[N],col[N];
bool cmp(node aa,node bb)
{
if(spl[aa.l] == spl[bb.l]) return aa.r < bb.r;
else return spl[aa.l] < spl[bb.l];
}
void modify(int x,int val)
{
if(col[x] > k) return ;
res += 2 * val * cnt[col[x]] + 1;
cnt[col[x]] += val;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&k);
for(int i = 1;i <= n;i++) scanf("%lld",&col[i]);
block = sqrt(n);
for(int i = 1;i <= block;i++)
{
L[i] = (i - 1) * block + 1;
R[i] = i * block;
}
if(R[block] < n) block++,L[block] = R[block - 1] + 1,R[block] = n;
for(int i = 1;i <= block;i++)
for(int j = L[i];j <= R[i];j++)
spl[j] = i;
for(int i = 1;i <= m;i++)
{
scanf("%lld%lld",&q[i].l,&q[i].r);
q[i].id = i;
}
sort(q + 1,q + m + 1,cmp);
for(int i = 1;i <= m;i++)
{
while(l > q[i].l) modify(--l,1);
while(r < q[i].r) modify(++r,1);
while(l < q[i].l) modify(l++,-1);
while(r > q[i].r) modify(r--,-1);
ans[q[i].id] = res;
}
for(int i = 1;i <= m;i++) printf("%lld\n",ans[i]);
return 0;
}
普通莫队优化——奇偶化排序
奇偶化排序即对于属于奇数块的询问,\(r\) 按从小到大排序,对于属于偶数块的排序,\(r\) 从大到小排序,这样我们的 \(r\) 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 \(n\) 移动处理下一个奇数块的问题,优化了 \(r\) 指针的移动次数,一般情况下,这种优化能让程序快 \(30\%\) 左右。
bool cmp(node aa,node bb)
{
if(spl[aa.l] != spl[bb.l]) return spl[aa.l] < spl[bb.l];
if(spl[aa.l] & 1) return aa.r < bb.r;
else return aa.r > bb.r;
}

浙公网安备 33010602011771号