莫队练习题

题目大意:求查询区间内异或和等于k的区间个数

难度:较简单

和这题类似有这道:CF86D Powerful array洛谷P2709 小B的询问

1、l 到 r 的区间异或和 等于 1 到 r 的异或和 异或 1 到 l 的异或和,所以储存异或前缀和即可
2、遍历到pos位时,当前等于k的区间 等于 区间异或和等于 a[pos]^k 的个数,所以用一个mp储存一下异或出现的结果个数(最好用数组,map或set可能会超时)
3、增区间,先算贡献;减区间,先算影响,后算贡献;

const int maxn=1e5+7;
int n,m,size,k,a[maxn];
ll ans[maxn],mp[maxn*20],sum;
struct node{
	int l,r,id;
	bool operator<(const node &a)const{
		if(l/size!=a.l/size)	return l<a.l;
		else if((l/size)&1)		return r<a.r;
		else	return r>a.r;
	}
}q[maxn];
int main(){
	n=read();	m=read();	k=read();	size=sqrt(n);
	for(int i=1;i<=n;i++){
		a[i]=read();
		a[i]^=a[i-1];
	}
	for(int i=0;i<m;i++){
		q[i].l=read()-1;	q[i].r=read();
		q[i].id=i;
	}
	sort(q,q+m);
	int l=1,r=0;
	for(int i=0;i<m;i++){
		int x=q[i].l,y=q[i].r;
		while(l<x){
			mp[a[l]]--;
			sum-=mp[a[l]^k];
			l++;
		}
		while(l>x){
			l--;
			sum+=mp[a[l]^k];
			mp[a[l]]++;
		}
		while(r<y){
			r++;
			sum+=mp[a[r]^k];
			mp[a[r]]++;
		}
		while(r>y){
			mp[a[r]]--;
			sum-=mp[a[r]^k];
			r--;
		}
		ans[q[i].id]=sum;
	}
	for(int i=0;i<m;i++)	out(ans[i]),putchar('\n');
}

这种题一般要推导出一个公式

可以看出莫队维护便是平方数,更改些地方,就出来了

const int maxn=1e6+7;
int n,m,a[maxn],size,mp[maxn];
ll ans1[maxn],ans2[maxn],sum;
struct node{
	int l,r,id;
	bool operator<(const node &a)const{
		if(l/size!=a.l/size)	return l<a.l;
		else if((l/size)&1)		return r<a.r;
		else	return r>a.r;
	}
}q[maxn];
void add(int i){
	sum-=mp[a[i]]*mp[a[i]];
	mp[a[i]]++;
	sum+=mp[a[i]]*mp[a[i]];
}
void sub(int i){
	sum-=mp[a[i]]*mp[a[i]];
	mp[a[i]]--;
	sum+=mp[a[i]]*mp[a[i]];
}
int main(){
	n=read();	m=read();	size=sqrt(n);
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=0;i<m;i++){
		q[i].l=read();	q[i].r=read();
		q[i].id=i;
	}
	sort(q,q+m);
	int l=1,r=0;
	for(int i=0;i<m;i++){
		int x=q[i].l,y=q[i].r,k=q[i].id;
		while(l<x)	sub(l++);
		while(l>x)	add(--l);
		while(r<y)	add(++r);
		while(r>y)	sub(r--);
		ans1[k]=sum-1ll*(r-l+1);
		ans2[k]=1ll*(r-l+1)*(r-l);
		if(!ans1[k]){
			ans2[k]=1;
			continue;
		}
		ll tmp=__gcd(ans1[k],ans2[k]);
		ans1[k]/=tmp;	ans2[k]/=tmp;
	}
	for(int i=0;i<m;i++)	printf("%lld/%lld\n",ans1[i],ans2[i]);
}

题目大意:A、B玩尼姆博弈,A先选一个大区间[ L , R ] ,B再在这个大区间选一个小区间[ l , r ] ,最后在这个小区间内尼姆博弈,区间异或和不等于0时,先手A必胜。同时加了个操作,给出pos位,交换pos位和pos+1位的数。
精简一下: 给你n个数的数组,m次操作。操作1:将x与x+1位的数字交换 ;操作2:询问 [L,R]多少个子区间[l,r]里面的所有数值异或起来不等于0

带修莫队,块大小size=(int)pow(n,2.0/3.0)

mp数组记录异或和为x的次数,因为是前缀异或和,查询的区间的左区间自动减一。

int n,m,a[maxn],size,b[maxn],c1,c2,p[maxn];
ll mp[maxn*10],ans[maxn],sum;
struct node{
	int l,r,id,time;
	bool operator<(const node &a)const{
		if(l/size!=a.l/size)	return l<a.l;
		else if(r/size!=a.r/size)		return r<a.r;
		else	return time<a.time;
	}
}q[maxn];
void add(int i){	sum+=mp[b[i]]++;	}
void sub(int i){	sum-=--mp[b[i]];	}
void change(int i,int t){
	int pos=p[t];
	bool f=(q[i].l<=pos&&pos<=q[i].r);
	if(f)	sub(pos);
	b[pos]^=a[pos];	swap(a[pos],a[pos+1]);	b[pos]^=a[pos];
	if(f)	add(pos);
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		memset(mp,0,sizeof(mp));	sum=c1=c2=0;
		size=(int)pow(n,2.0/3.0);
		for(int i=1;i<=n;i++){
			a[i]=read();
			b[i]=b[i-1]^a[i];
		}
		while(m--){
			int op=read();
			if(op==1){
				q[c1].l=read()-1;	q[c1].r=read();
				q[c1].time=c2;	q[c1].id=c1;
				c1++;
			}
			else	p[++c2]=read();
		}
		sort(q,q+c1);
		int l=1,r=0,t=0;
		for(int i=0;i<c1;i++){
			int x=q[i].l,y=q[i].r,k=q[i].id,tmp=q[i].time;
			while(l<x)	sub(l++);
			while(l>x)	add(--l);
			while(r<y)	add(++r);
			while(r>y)	sub(r--);
			while(t<tmp)	change(i,++t);
			while(t>tmp)	change(i,t--);
			ans[k]=(ll)(y-x+1)*(y-x)/2-sum;
		}
		for(int i=0;i<c1;i++)	out(ans[i]),putchar('\n');
	}
}

虽然融合了莫队的两大知识点(带修+树上),但这套题很适合做模板
涉及到:
1、带修排序方式,先 l ,后 r ,最后 time。
2、时间戳
3、欧拉序的规律
4、倍增求lca

const int maxn=2e5+7;
int n,m,q,size,c1,c2;
int v[maxn],w[maxn],a[maxn],mp[maxn],vis[maxn];
int tot,d[maxn],head[maxn],ft[maxn],lt[maxn],fa[maxn][30],ou[maxn],c;
ll sum,ans[maxn];
struct edge{	int to,next;	}e[maxn];
struct point{	int id,val;	}p[maxn];
struct node{
	int l,r,id,time,lca;
	bool operator<(const node &a)const{
		if(l/size!=a.l/size)	return l<a.l;
		else if(r/size!=a.r/size)		return r<a.r;
		else	return time<a.time;
	}
}sz[maxn];
void Add(int u,int v){
	e[++tot]=(edge){v,head[u]};		head[u]=tot;
	e[++tot]=(edge){u,head[v]};		head[v]=tot;
}
void dfs(int x){
	ou[++c]=x;	ft[x]=c;
	for(int i=head[x];i;i=e[i].next){
		int t=e[i].to;
		if(t==fa[x][0])	continue;
		d[t]=d[x]+1;	fa[t][0]=x;
		for(int j=1;(1<<j)<=d[t];j++)	fa[t][j]=fa[fa[t][j-1]][j-1];
		dfs(t);
	}
	ou[++c]=x;	lt[x]=c;
}
int Lca(int u,int v){
	if(d[u]<d[v])	swap(u,v);
	for(int i=20;i>=0;i--)
		if(d[u]-(1<<i)>=d[v])	u=fa[u][i];
	if(u==v)	return u;
	for(int i=20;i>=0;i--)
		if(fa[u][i]!=fa[v][i])	u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void add(int i){	sum+=1ll*v[i]*w[++mp[i]];	}
void sub(int i){	sum-=1ll*v[i]*w[mp[i]--];	}
void cc(int i){
	vis[i]^=1;
	if(vis[i])	add(a[i]);
	else		sub(a[i]);
}
void change(int t){
	int tmp=p[t].id;
	if(vis[tmp])	sub(a[tmp]);
	swap(a[tmp],p[t].val);
	if(vis[tmp])	add(a[tmp]);
}
int main(){
	n=read();	m=read();	q=read();
	size=(int)pow(n,2.0/3.0);
	for(int i=1;i<=m;i++)	v[i]=read();
	for(int i=1;i<=n;i++)	w[i]=read();
	for(int i=1;i<n;i++)	Add(read(),read());
	for(int i=1;i<=n;i++)	a[i]=read();
	d[1]=1;		dfs(1);
	while(q--){
		int op=read(),x=read(),y=read();
		if(op==1){
			if(d[x]>d[y])	swap(x,y);
			int lca=Lca(x,y);
			if(lca==x)	sz[c1]=(node){ft[x],ft[y],c1,c2,0};
			else		sz[c1]=(node){lt[x],ft[y],c1,c2,lca};
			c1++;
		}
		else	p[++c2]=(point){x,y};
	}
	sort(sz,sz+c1);
	int l=1,r=0,t=0;
	for(int i=0;i<c1;i++){
		int x=sz[i].l,y=sz[i].r,tmp=sz[i].time,lca=sz[i].lca,k=sz[i].id;
		while(l<x)	cc(ou[l++]);
		while(l>x)	cc(ou[--l]);
		while(r<y)	cc(ou[++r]);
		while(r>y)	cc(ou[r--]);
		while(t<tmp)	change(++t);
		while(t>tmp)	change(t--);
		if(lca)	cc(lca);
		ans[k]=sum;
		if(lca)	cc(lca);
	}
	for(int i=0;i<c1;i++)	out(ans[i]),putchar('\n');
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值