莫队例题

莫队例题

一:[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的数有哪些,开个桶暴力做就行了。

最后提醒两点:

  1. 要注意莫队求出的是与上一次的变化,最后还要算个前缀和才是最后的答案。
  2. 在移动左指针时,例如将 \(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]);
}
posted @ 2021-08-01 16:42  ☄️ezuyz☄️  阅读(52)  评论(0)    收藏  举报