莫队
本质上是双指针加分块,可在 $O(n\sqrt{m})$ 的时间复杂度下解决大部分区间查询问题,比线段树好写。
板子部分:
//全局作用域该初始化的东西
const int MAXN=1e5+5;
struct Mo{
int l,r,i;
}ask[MAXN];//用于记录询问
int pos[MAXN];//用于记录所属块(优化sort用的)
ll ans[MAXN];//用于记录答案
ll res;//用于传递当前查询的答案
void ADD(int x){//区间加入新元素时更新res用的函数
}
void SUB(int x){//区间删除元素时更新res用的
}
//solve()函数内该写的东西
void solve(){
CIN(n);CIN(q);
int siz=sqrt(n);//根号分块
FOR(i,1,n){cin>>a[i];pos[i]=i/siz;}//读入数据时顺便初始化一下块信息
FOR(i,1,q){//强制离线
cin>>ask[i].l>>ask[i].r;
ask[i].i=i;
}
sort(ask+1,ask+1+q,[&](Mo a,Mo b){//按块排序,归属同一块的比较右端点大小,不同块的比较快位置
return pos[a.l]==pos[b.l]?a.r<b.r:pos[a.l]<pos[b.l];
});
int l=1,r=0;//依次处理排序后的询问
FOR(i,1,q){
while(ask[i].l<l)ADD(--l);//非常板的部分,理解记忆即可
while(ask[i].l>l)SUB(l++);
while(ask[i].r>r)ADD(++r);
while(ask[i].r<r)SUB(r--);
ans[ask[i].i]=res;//收集答案
}
FOR(i,1,q)cout<<ans[i]<<ET;//输出答案,注意题目格式要求
}
举例: 洛谷P2709 小B的询问
#include<iostream>
#include<vector>
#include<cmath>
#include<set>
#include<array>
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
#define ET '\n'
#define FOR(i,a,b) for(ll i=(a);i<=(b);i++)
#define rFOR(i,a,b) for(ll i=(a);i>=(b);i--)
//趋向for循环,一般情况别用,规定方向时会跳出的循环在这里不会跳出
#define rep(i,a,b) for(ll i=(a);i!=(b)+2*(a<b)-1;i+=2*(a<b)-1)
#define CIN(a) ll a;cin>>a
#define DCIN(a) ld a;cin>>a
#define SCIN(a) string a;cin>>a
#define CA cout<<ans<<ET
#define CY cout<<"YES"<<ET
#define CN cout<<"NO"<<ET
#define max(a,b) ((a>b)?(a):(b))
#define min(a,b) ((a<b)?(a):(b))
std::mt19937 myrand(time(0));
ll rnd(ll l, ll r) {return std::uniform_int_distribution<ll>(l, r)(myrand);}
//没有C++20就不能用ranges了//CF又可以用C++20了,封印解除
#define PP(l,r,CK) *ranges::partition_point(ranges::iota_view((l),(r)+1),(CK))
int M=1e9+7;
inline ll MO(ll x){return (x%M+M)%M;}
string ANS[2]={"No\n","Yes\n"};
const int MAXN=1e5+5;
int cnt[MAXN],a[MAXN];//使用cnt作为一个桶记录元素出现次数
struct Mo{
int l,r,i;
}ask[MAXN];
int pos[MAXN];
ll ans[MAXN];
ll res;
void ADD(int x){
res-=cnt[a[x]]*cnt[a[x]];//先从res答案中剔除未修改部分
cnt[a[x]]++;//再加回来
res+=cnt[a[x]]*cnt[a[x]];
}
void SUB(int x){
res-=cnt[a[x]]*cnt[a[x]];
cnt[a[x]]--;
res+=cnt[a[x]]*cnt[a[x]];
}
void solve(){
CIN(n);CIN(q);CIN(k);
int siz=sqrt(n);
FOR(i,1,n){cin>>a[i];pos[i]=i/siz;}
FOR(i,1,q){
cin>>ask[i].l>>ask[i].r;
ask[i].i=i;
}
sort(ask+1,ask+1+q,[&](Mo a,Mo b){
return pos[a.l]==pos[b.l]?a.r<b.r:pos[a.l]<pos[b.l];
});
int l=1,r=0;
FOR(i,1,q){
while(ask[i].l<l)ADD(--l);
while(ask[i].l>l)SUB(l++);
while(ask[i].r>r)ADD(++r);
while(ask[i].r<r)SUB(r--);
ans[ask[i].i]=res;
}
FOR(i,1,q)cout<<ans[i]<<ET;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//pre();
//CIN(t);while(t--)
solve();
return 0;
}
#include<iostream>
#include<vector>
#include<cmath>
#include<set>
#include<array>
#include<bits/stdc++.h>
#define ll long long
#define ld long double
using namespace std;
#define ET '\n'
#define FOR(i,a,b) for(ll i=(a);i<=(b);i++)
#define rFOR(i,a,b) for(ll i=(a);i>=(b);i--)
//趋向for循环,一般情况别用,规定方向时会跳出的循环在这里不会跳出
#define rep(i,a,b) for(ll i=(a);i!=(b)+2*(a<b)-1;i+=2*(a<b)-1)
#define CIN(a) ll a;cin>>a
#define DCIN(a) ld a;cin>>a
#define SCIN(a) string a;cin>>a
#define CA cout<<ans<<ET
#define CY cout<<"YES"<<ET
#define CN cout<<"NO"<<ET
#define max(a,b) ((a>b)?(a):(b))
#define min(a,b) ((a<b)?(a):(b))
std::mt19937 myrand(time(0));
ll rnd(ll l, ll r) {return std::uniform_int_distribution<ll>(l, r)(myrand);}
//没有C++20就不能用ranges了//CF又可以用C++20了,封印解除
#define PP(l,r,CK) *ranges::partition_point(ranges::iota_view((l),(r)+1),(CK))
int M=1e9+7;
inline ll MO(ll x){return (x%M+M)%M;}
string ANS[2]={"No\n","Yes\n"};
const int MAXN=5e4+5;
ll a[MAXN];
int pos[MAXN];
int cnt[MAXN];
pair<int,int> ans[MAXN];int res;
struct Mo{
int l,r,i;
}ask[MAXN];
void ADD(int x){
res-=(cnt[a[x]])*(cnt[a[x]]-1)/2;
cnt[a[x]]++;
res+=(cnt[a[x]])*(cnt[a[x]]-1)/2;
}
void SUB(int x){
res-=(cnt[a[x]])*(cnt[a[x]]-1)/2;
cnt[a[x]]--;
res+=(cnt[a[x]])*(cnt[a[x]]-1)/2;
}
void print(ll a,ll b){
b=b*(b-1)/2;
if(a==0)cout<<"0/1\n";
else {
ll d=__gcd(a,b);
cout<<a/d<<"/"<<b/d<<ET;
}
}
void solve(){
CIN(n);CIN(m);
int siz=sqrt(n);
FOR(i,1,n){cin>>a[i];pos[i]=i/siz;}
FOR(i,1,m){
cin>>ask[i].l>>ask[i].r;ask[i].i=i;
}
sort(ask+1,ask+1+m,[&](Mo a,Mo b){
return pos[a.l]==pos[b.l]?a.r<b.r:pos[a.l]<pos[b.l];
});
int l=1,r=0;
FOR(i,1,m){
while(ask[i].l<l)ADD(--l);
while(ask[i].r>r)ADD(++r);
while(ask[i].l>l)SUB(l++);
while(ask[i].r<r)SUB(r--);
ans[ask[i].i]={res,ask[i].r-ask[i].l+1};
}
FOR(i,1,m)print(ans[i].first,ans[i].second);
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//pre();
//CIN(t);while(t--)
solve();
return 0;
}
待填坑:带修莫队,回滚莫队,树上莫队,二次离线莫队
参考博客: