莫队
莫队(离线算法)
离线算法:收集所有问题,同意计算答案。
应用场景:多次区间询问、区间扩张、区间收缩。
莫队:对询问排序(先对序列分块
- 第一关键字:按左边界的块号从小到大排序
- 第二关键字:按右边界从小到大排序
莫队是一个很好写的高端暴力方式,但有时确实无法被替代。
【板】P3901 数列找不同
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e5+5;
int n, q, val[MAX], ans[MAX], t[MAX], res, len; // res 为当前区间信息,ans[] 为每一个问题的答案
struct Qry{int l, r, id;} qry[MAX]; // 询问
bool cmp(Qry a,Qry b)
{
int ida = (a.l - 1) / len + 1; // 计算第一个区间的左边界块号
int idb = (b.l - 1) / len + 1; // 计算第二个区间的左边界块号
return ida < idb || (ida == idb && a.r < b.r);
}
void add(int pos)
{
t[val[pos]] += 1;
if(t[val[pos]] > 1) res += 1;
}// 加入对应元素并且重算区间信息。
void del(int pos)
{
t[val[pos]] -= 1;
if(t[val[pos]] >= 1) res -= 1;
}// 去除对应元素并且重算区间信息。
void mo()
{
len = sqrt(n); // 计算块长
sort(qry + 1, qry + q + 1, cmp);
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(r > qry[i].r) del(r--);
while(l < qry[i].l) del(l++);
ans[qry[i].id] = res;
}
}
int main()
{
cin >> n >> q;
for(int i=1; i<=n; i++) cin >> val[i];
for(int i=1; i<=q; i++) cin >> qry[i].l >> qry[i].r, qry[i].id = i;
mo();
for(int i=1; i<=q; i++)
{
if(ans[i]) cout << "No" << endl;
else cout << "Yes" << endl;
}
return 0;
}
带修莫队
【板】P1494 [国家集训队] 小 Z 的袜子
#include<bits/stdc++.h>
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAX = 1e5+5;
int n, q, val[MAX], ans1[MAX], ans2[MAX], num[MAX], sum, len; // res 为当前区间信息,ans[] 为每一个问题的答案
struct Qry{int l, r, id;} qry[MAX]; // 询问
bool cmp(Qry a,Qry b)
{
int ida = (a.l - 1) / len + 1; // 计算第一个区间的左边界块号
int idb = (b.l - 1) / len + 1; // 计算第二个区间的左边界块号
return ida < idb || (ida == idb && a.r < b.r);
}
void add(int pos)
{
sum += num[val[pos]];
++num[val[pos]];
}// 加入对应元素并且重算区间信息。
void del(int pos)
{
--num[val[pos]];
sum -= num[val[pos]];
}// 去除对应元素并且重算区间信息。
void mo()
{
len = sqrt(n); // 计算块长
sort(qry + 1, qry + q + 1, cmp);
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(r > qry[i].r) del(r--);
while(l < qry[i].l) del(l++);
ans1[qry[i].id] = sum;
ans2[qry[i].id] = (r - l + 1) * (r - l) / 2;
if(ans1[qry[i].id] == 0) ans2[qry[i].id] = 1;
else
{
int g = __gcd(ans1[qry[i].id], ans2[qry[i].id]);
ans1[qry[i].id] /= g;
ans2[qry[i].id] /= g;
}
}
}
signed main()
{
cin >> n >> q;
for(int i=1; i<=n; i++) cin >> val[i];
for(int i=1; i<=q; i++) cin >> qry[i].l >> qry[i].r, qry[i].id = i;
mo();
for(int i=1; i<=q; i++) cout << ans1[i] << '/' << ans2[i] << endl;
return 0;
}
回滚莫队
【板】P5906 【模板】回滚莫队&不删除莫队
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n, q, l, r, val[N], ans[N], res, len, maxx[N], minn[N], block[N]; // res 为当前区间信息,ans[] 为每一个问题的答案
struct Qry {int l, r, id;} qry[N]; // q 个询问
bool cmp(Qry a,Qry b) {return block[a.l] < block[b.l] || (block[a.l] == block[b.l] && a.r < b.r);}
struct His {int x, max_w, min_w, pre_res;}; stack<His> his; // History
void Discretize(int *a,int n)
{
vector<int> tmp;
for(int i=1; i<=n; ++i) tmp.push_back(a[i]);
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
for(int i=1; i<=n; ++i) a[i] = lower_bound(tmp.begin(), tmp.end(), a[i]) - tmp.begin() + 1;
} // 离散化
void add(int pos, bool save)
{
int w = val[pos];
if(save == true) his.push({w, maxx[w], minn[w], res});
// 记录原本的值
maxx[w] = max(maxx[w], pos);
minn[w] = min(minn[w], pos);
res = max(res, maxx[w] - minn[w]);
} // 加入对应元素并且重算区间信息,save表示是否记录信息
void clear()
{
for(int i=1; i<=n; ++i) maxx[i] = 0, minn[i] = n + 1;
res = 0;
} // 清空记录信息,可支持 O(n) 清空
void roll_back()
{
while(his.empty() == false)
{
auto [x, max_w, min_w, pre_res] = his.top();
his.pop();
maxx[x] = max_w;
minn[x] = min_w;
res = pre_res;
}
} // 全部回滚
void rollback_mo()
{
Discretize(val, n);
len = sqrt(n);
// 预处理 全下标块号
for(int i=1; i<=n; ++i) block[i] = (i - 1) / len + 1;
sort(qry + 1, qry + q + 1, cmp);
for(int i = 1, l, r; i <= q; ++i)
{
// 到达新块:1.重置起点;2.重置信息。
if(block[qry[i].l] != block[qry[i - 1].l]) r = min(n, block[qry[i].l] * len), l = r + 1, clear();
// 正常 点
if(block[qry[i].l] != block[qry[i].r])
{
while(r < qry[i].r) add(++r, false); // 右边界对齐,不记录
int ori_l = l;
while(l > qry[i].l) add(--l, true); // 左边界对齐,记录
l = ori_l; // 左边界还原
}
// 对角线旁的点暴力,记录
// 因为对角线旁的点处理在正常点之前,此时区间信息为空,所以可以拿来复用一下
else for(int j = qry[i].l; j <= qry[i].r; ++j) add(j, true);
ans[qry[i].id] = res;
roll_back(); // 回滚操作
}
}
signed main()
{
ios::sync_with_stdio(false);
cin >> n; for(int i = 1;i <= n;++i) cin >> val[i];
cin >> q; for(int i = 1;i <= q;++i) cin >> l >> r, qry[i] = {l, r, i};
rollback_mo();
for(int i = 1;i <= q;++i) cout << ans[i] << endl;
return 0;
}