莫队基础
莫队
对于一些区间问题如果已经知道 \([l,r]\) 区间问题的答案,那么可以通过加减 \(l\) ,\(r\) 两个节点的信息,得到 \([l+1,r]\),\([l-1,r]\),\([l,r-1]\),\([l,r+1]\),这样就可以的到所有区间查询的答案
具体做法是离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。
排序方法是以 \(l\) 为第一关键字划分成多个「块」 ,如果 \(l\) 在一个「块」内就按 \(r\) 从小到大排序,不然就按 \(l\) 从小到大排序
这样排序可以保证区间移动的长度最短,时间更快
时间复杂度
- 「块」内移动,因为左端点都在一个块内,所以单词移动是 \(O(\sqrt{n})\) 因为有 \(q\) 次询问所以就是 \(q*\sqrt{n}\)
- 「块」外移动是 \(O(\sqrt{n})\) 因为最多是 \(\sqrt{n}\) 个块,所以总复杂度是 \(O(n)\),而右指针移动跨「块」时可能会掉递增,往回走,因为「块」有\(\sqrt{n}\)个,这种行为最多有\(\sqrt{n}\)次,每次有\(O(n)\)步,所以外部的总复杂的是 \(O(n\,\sqrt{n})\)
- 最后总复杂度就是 \(O((n+q)\,\sqrt{n})\)
P3901 数列找不同
莫队直接做,在移动时当前区间移动后信息的变化,维护区间的不同点的种类数:
- \(l<查询的左端点\),\(l\) 往右移动,同时删除 \(l\) 这个点的信息
- \(l>查询的左端点\),\(l\) 往左移动,同时添加 \(l\) 这个点的信息
- \(r<查询的右端点\),\(r\) 往右移动,同时添加 \(r\) 这个点的信息
- \(r>查询的右端点\),\(r\) 往左移动,同时删除 \(r\) 这个点的信息
添加和删除维护当前的点的种类数
- 添加(ADD):将添加的点的出现次数\(++\),如果是第一次出现,那么就种类数\(++\)
- 删除(SUB):将删除的点的出现次数\(--\),如果出现次数为变\(0\),那么种类术\(--\)
最后对于每一次查询,如果点的种类数正好是区间长度,那么说明每个点都互不相同
#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=2e5+10;
struct Query{
int l,r,id,ans;
}q[N];
int tot=0;
int a[N],n,m,len,mp[N];
bool cmp(Query A,Query B){
if(A.l/len!=B.l/len)
return A.l<B.l;
return A.r<B.r;
}
bool out(Query A,Query B){
return A.id<B.id;
}
void SUB(int x){
mp[a[x]]--;
if(mp[a[x]]==0)tot--;
return;
}
void ADD(int x){
mp[a[x]]++;
if(mp[a[x]]==1)tot++;
return;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(l<q[i].l)SUB(l++);
while(l>q[i].l)ADD(--l);
while(r<q[i].r)ADD(++r);
while(r>q[i].r)SUB(r--);
q[i].ans=(tot==(q[i].r-q[i].l+1));
}
sort(q+1,q+m+1,out);
for(int i=1;i<=m;i++)cout<<(q[i].ans? "Yes":"No")<<'\n';
return 0;
}
P2709 小B的询问
区间的移动方式和上一题一样,但是维护的是一个区间的答案,加减函数和原来不同,依然要记录每个点的出现次数
- 加(ADD)向把和减去当前出现次数的平方,再把出现次数\(+1\),把和加回去
- 减(SUB)类似
#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=2e5+10;
struct Query{
int l,r,id,ans;
}q[N];
int sum=0;
int a[N],n,m,k,len,mp[N];
bool cmp(Query A,Query B){
if(A.l/len!=B.l/len)
return A.l<B.l;
return A.r<B.r;
}
bool out(Query A,Query B){
return A.id<B.id;
}
void SUB(int x){
sum-=(mp[a[x]]*mp[a[x]]);
mp[a[x]]--;
sum+=(mp[a[x]]*mp[a[x]]);
return;
}
void ADD(int x){
sum-=(mp[a[x]]*mp[a[x]]);
mp[a[x]]++;
sum+=(mp[a[x]]*mp[a[x]]);
return;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m>>k;
len=sqrt(n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(l<q[i].l)SUB(l++);
while(l>q[i].l)ADD(--l);
while(r<q[i].r)ADD(++r);
while(r>q[i].r)SUB(r--);
q[i].ans=sum;
}
sort(q+1,q+m+1,out);
for(int i=1;i<=m;i++)cout<<q[i].ans<<'\n';
return 0;
}
P1494 [国家集训队] 小 Z 的袜子
和前两题类似,记录每个颜色的出现次数,维护颜色的种类数
每一次查询的概率用分数表示,分子就只对于每一种颜色的出现次数 \(x\),那么摸到两个这个颜色的概率就是\(x*(x-1)\)
把所有颜色的概率加加起来就是这次询问的分子
而分母就是区间长度\(len*(len-1)\),约分把分子分母同时除以\(gcd\)
#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=2e5+10;
struct Query{
int l,r,id,ans;
}q[N];
int a[N],n,m,len;
int sum;
int mp[N];
bool cmp(Query A,Query B){
if(A.l/len!=B.l/len)
return A.l<B.l;
return A.r<B.r;
}
bool out(Query A,Query B){
return A.id<B.id;
}
void SUB(int x){
sum-=(mp[a[x]]*(mp[a[x]]-1));
mp[a[x]]--;
if(mp[a[x]]>0)sum+=(mp[a[x]]*(mp[a[x]]-1));
return;
}
void ADD(int x){
if(mp[a[x]]>0)sum-=(mp[a[x]]*(mp[a[x]]-1));
mp[a[x]]++;
sum+=(mp[a[x]]*(mp[a[x]]-1));
return;
}
int gcd(int x,int y){
if(!y)return x;
return gcd(y,x%y);
}
signed main(){
ios::sync_with_stdio(false);
// freopen("P1494.in","r",stdin);
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
if(q[i].l==q[i].r){
q[i].ans=0;
continue;
}
while(l<q[i].l)SUB(l++);
while(l>q[i].l)ADD(--l);
while(r<q[i].r)ADD(++r);
while(r>q[i].r)SUB(r--);
q[i].ans=sum;
}
sort(q+1,q+m+1,out);
for(int i=1;i<=m;i++){
if(q[i].l==q[i].r){
cout<<0<<'/'<<1<<'\n';
continue;
}
int a=q[i].ans;
int b=(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
cout<<a/(gcd(a,b))<<'/'<<b/gcd(a,b)<<'\n';
}
return 0;
}
P4462 [CQOI2018] 异或序列
计算异或前缀和,一段区间的异或和\(sum(l,r)\)就是\(val[r] \oplus val[l-1]\)
题目需要\(sum(l,r)==k\) 也就是
把\(val[l-1]\)移到右边
这样就可以转化成和之前一样的统计每一个 \(val[i]\) 的值的出现次数对于每一个
\(val[r]\)符合条件的数的个数,就是\(val[r] \oplus k\) 这个值的出现次数
#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=1e5+10;
int n,m,k,len,sum;
int a[N],val[N];
int cnt[N];
struct Query{
int l,r,id,ans;
}q[N];
bool cmp(Query A,Query B){
if(A.l/len!=B.l/len)
return A.l<B.l;
return A.r<B.r;
}
bool out(Query A,Query B){
return A.id<B.id;
}
void ADD(int x){
sum+=cnt[val[x]^k];
cnt[val[x]]++;
return;
}
void SUB(int x){
cnt[val[x]]--;
sum-=cnt[val[x]^k];
return;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m>>k;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
val[i]=val[i-1]^a[i];
}
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
cnt[0]=1;
for(int i=1;i<=m;i++){
while(l<q[i].l){SUB(l-1);l++;}
while(l>q[i].l){l--,ADD(l-1);}
while(r<q[i].r)ADD(++r);
while(r>q[i].r)SUB(r--);
q[i].ans=sum;
}
sort(q+1,q+m+1,out);
for(int i=1;i<=m;i++){
cout<<q[i].ans<<'\n';
}
return 0;
}

浙公网安备 33010602011771号