莫队例题
莫队例题
一:[CQOI2018]异或序列
题意:
给定查询参数l、r,问在al到ar的区间内,有多少子序列满足异或和等于k。对于所有询问,k都相同。
做法:
通过前缀和,将问题转化为在一个区间内有多少对数对的异或值为k,直接套用莫队即可。
*二:[SNOI2017]一个简单的询问
题意:
求 get(l1,r1,x)×get(l2,r2,x) ,x为任意正整数
get(l,r,x) 表示计算区间 [[l,r] 中,数字 x 出现了多少次。
做法:
先将区间问题转化为前缀问题,设
g(i,x)=get(1,i,x)
原问题即转化为
g(r1,x)g(r2,x)−g(r1,x)g(l2−1,x)−g(l1−1,x)g(r2,x)+g(l1−1,x)g(l2−1,x)
于是将一个询问拆成四个区间询问,即可套用莫队。
注意此时的莫队含义有所改变,两个指针不再是一个区间的左右端点,而是两个指针,指向两个g(i,x)中i的位置。
*三【模板】莫队二次离线(第十四分块(前体))
题意:
给了你一个序列 a,每次查询给一个区间 [l,r]
查询 l≤i<j≤r ,且 ai⊕aj 的二进制表示下有 k 个 1 的二元组 (i,j) 的个数。⊕是指按位异或.
做法:
指针每移动一下,即为求新添加(或删除)的元素与序列有多少个数异或起来为k。
设 \(g(x,l,r)\) 表示第 x 位的数在 \((l,r)\) 这个区间中有多少个数异或起来为 k,
可以进行问题拆分。
假如是将r向右扩展一个数,即原答案加上 \(g(r+1,l,r)\),可转化为加上 \(g(r+1,1,r)\)再减去 \(g(r+1,1,l-1)\). 前者可以预处理,而后者可以先扔到 \(l-1\) 处的 vetor 中,最后一起处理,而一次性的多次移动(即 r 一直移动到 qr ),前者可预处理出前缀和直接全加上,后者可以一起扔进 vector ,这样在莫队时指针从 r 移动到 qr,也就用了 \(o(1)\) 的时间,但后期一起处理时后者的复杂度可以看作指针移动的距离。
其余移动类似。
至于如何预处理和总的最后处理,先找出一共有k个1的数有哪些,开个桶暴力做就行了。
最后提醒两点:
- 要注意莫队求出的是与上一次的变化,最后还要算个前缀和才是最后的答案。
- 在移动左指针时,例如将 \(l\) 移动到 \(l-1\) ,我们将 \(l-1\) 直接扔进了 r 的 \(vetor\) 中,这样如果 k 等于 0,就出现了 \((l-1,l-1)\) 这个二元组,与题意不符,因此如果 k 为 0 要消除这部分影响,即在移动左指针时加上或减去指针移动的次数即可。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
#define ll long long
struct qw{
int ql,qr,id;
ll res;
} q[N];
int bl[N];
int n,m,k;
int cmp(qw x,qw y)
{
if(bl[x.ql]^bl[y.ql]) return bl[x.ql]<bl[y.ql];
if(bl[x.ql]&1) return x.qr<y.qr;
return x.qr>y.qr;
}
int a[N];
int pp,p[20008];
ll ans[N],g[N],f[N],s[N];
struct tw{
int ql,qr,id,v;
};
vector<tw> range[N];
int get(int n)
{
int s=0;
while(n) s++,n-=(n&-n);
return s;
}
void pre_y()
{
for(int i=0;i<=(1<<14)-1;i++)
if(get(i)==k) p[++pp]=i;;
}
void MD()
{
int l=1,r=0;
for(int i=1;i<=m;i++)
{
int ql=q[i].ql,qr=q[i].qr;
if(r<qr) range[l-1].push_back((tw){r+1,qr,i,-1}),q[i].res+=s[qr]-s[r],r=qr;
if(r>qr) range[l-1].push_back((tw){qr+1,r,i,1}),q[i].res-=s[r]-s[qr],r=qr;
if(l>ql) range[r].push_back((tw){ql,l-1,i,1}),q[i].res-=s[l-1]-s[ql-1]+(!k)*(l-ql),l=ql;
if(l<ql) range[r].push_back((tw){l,ql-1,i,-1}),q[i].res+=s[ql-1]-s[l-1]+(!k)*(ql-l),l=ql;
}
}
int main()
{
cin>>n>>m>>k;
pre_y();
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int siz=sqrt(n);
int nk=ceil((double)n/siz);
for(int i=1;i<=nk;i++)
for(int j=(i-1)*siz+1;j<=min(n,i*siz);j++)
bl[j]=i;
for(int j=1;j<=pp;j++)
g[a[1]^p[j]]++;
for(int i=2;i<=n;i++)
{
f[i]=g[a[i]];
s[i]=f[i]+s[i-1];
for(int j=1;j<=pp;j++)
g[a[i]^p[j]]++;
}
for(int i=1;i<=m;i++)
scanf("%d%d",&q[i].ql,&q[i].qr),q[i].id=i,q[i].res=0;
sort(q+1,q+m+1,cmp);
MD();
memset(g,0,sizeof(g));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=pp;j++)
g[a[i]^p[j]]++;
for(int j=0;j<range[i].size();j++)
for(int t=range[i][j].ql;t<=range[i][j].qr;t++)
q[range[i][j].id].res+=g[a[t]]*range[i][j].v;
}
for(int i=1;i<=m;i++) q[i].res+=q[i-1].res;
for(int i=1;i<=m;i++) ans[q[i].id]=q[i].res;
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
*四P3245 [HNOI2016]大数
题意:
每个询问求 S 的一个子串中有多少子串是 p 的倍数(0 也是 p 的倍数),p 为质数。
做法:
设 \(s[i]\) 表示从第 \(i\) 位开始的后缀对 p 的余数是几,那么如过 \(s[l]\) 与 \(s[r+1]\) 相同,那么 \(l\) 到 \(r\) 的子串(即两者的差)即为 p 的倍数,
因此处理出 s 数组后莫队即可。
这样做最后会发现 wa 了一个点,再考虑一下,发现当 p 为 2或5时,求出的s数组全为同一个数,出现很明显的错误,问题出在哪里?
例如 522 ,p 为 2,\(s[1]=s[2]=0\),实际上两者的差为500,但由于500也可以整除 2,这样就将 5 误判成了对的子串,因此 2和5要特殊处理,幸亏这里的 p为素数,否则不只 \(2,5\),\(10,25,100\) 这类数都会出现问题。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+8;
int p,n,m;
int a[N];
char s[N];
int f[N],bl[N];
map<int,int> tt;
int t[N];
struct ques{
int l,r,num;
} q[N];
int aans[N],ans=0;
int nt=0;
int l=1,r=0,ql,qr,op;
bool cmp(ques x,ques y)
{
if(bl[x.l]^bl[y.l]) return bl[x.l]<bl[y.l];
return bl[x.l]%2?x.r<y.r:x.r>y.r;
}
void ad(int x)
{
if(p==2||p==5)
{
if(op) nt+=(a[x]%p==0),ans+=nt;
else nt+=(a[x]%p==0),ans+=(r-l+1)*(a[x]%p==0);
return;
}
ans-=t[f[x]]*(t[f[x]]-1)/2;
t[f[x]]++;
ans+=t[f[x]]*(t[f[x]]-1)/2;
}
void del(int x)
{
if(p==2||p==5)
{
if(l!=ql) ans-=nt,nt-=(a[x]%p==0);
else ans-=(r-l+1)*(a[x]%p==0),nt-=(a[x]%p==0);
return;
}
ans-=t[f[x]]*(t[f[x]]-1)/2;
t[f[x]]--;
ans+=t[f[x]]*(t[f[x]]-1)/2;
}
signed main()
{
cin>>p;
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
int t0=1;
f[n+1]=0;
int tot=0;
for(int i=n;i>=1;i--)
{
f[i]=(a[i]*t0%p+f[i+1])%p;
t0=t0*10%p;
}
n++;
for(int i=1;i<=n;i++)
{
if(!tt[f[i]]) tt[f[i]]=++tot;
f[i]=tt[f[i]];
}
int siz=sqrt(n);
int nk=ceil((double)n/siz);
for(int i=1;i<=nk;i++)
for(int j=(i-1)*siz+1;j<=min(n,i*siz);j++)
bl[j]=i;
cin>>m;
for(int i=1;i<=m;i++)
scanf("%lld%lld",&q[i].l,&q[i].r),q[i].r++,q[i].num=i;
sort(q+1,q+1+m,cmp);
for(int i=1;i<=m;i++)
{
ql=q[i].l,qr=q[i].r;
if(p==2||p==5) qr--;
while(l<ql) del(l),++l;
while(l>ql) ad(--l),op=1;
while(r<qr) ad(++r),op=0;
while(r>qr) del(r),--r;
aans[q[i].num]=ans;
}
for(int i=1;i<=m;i++) printf("%lld\n",aans[i]);
}