莫队好题选讲

前置知识

莫队

例1:小Z的袜子

c n t [ i ] cnt[i] cnt[i]为颜色 i i i的出现次数,那么我们的答案为
∑ c n t [ i ] ( c n t [ i ] − 1 ) ( R − L + 1 ) ( R − L ) \frac{\sum cnt[i](cnt[i]-1)}{(R-L+1)(R-L)} (RL+1)(RL)cnt[i](cnt[i]1)
= ( c n t [ 1 ] 2 + c n t [ 2 ] 2 + . . . + c n t [ n ] 2 ) − ( c n t [ 1 ] + c n t [ 2 ] + . . . + c n t [ n ] ) ( R − L + 1 ) ( R − L ) =\frac{(cnt[1]^2+cnt[2]^2+...+cnt[n]^2)-(cnt[1]+cnt[2]+...+cnt[n])}{(R-L+1)(R-L)} =(RL+1)(RL)(cnt[1]2+cnt[2]2+...+cnt[n]2)(cnt[1]+cnt[2]+...+cnt[n])
= ( c n t [ 1 ] 2 + c n t [ 2 ] 2 + . . . + c n t [ n ] 2 ) − ( R − L + 1 ) ( R − L + 1 ) ( R − L ) =\frac{(cnt[1]^2+cnt[2]^2+...+cnt[n]^2)-(R-L+1)}{(R-L+1)(R-L)} =(RL+1)(RL)(cnt[1]2+cnt[2]2+...+cnt[n]2)(RL+1)

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct que{
	int l,r,id,pos;
	long long a,b;
}ans[50005];
int n,m,f[50005],base,nw=0,cnt[500005],pos[50005];
bool cmp1(que x,que y){
	if(pos[x.l]==pos[y.l])  return x.r<y.r;
	return x.l<y.l;
}
bool cmp2(que x,que y){
	return x.id<y.id;
}
void update(int p,int add)
{
    nw-=cnt[f[p]]*cnt[f[p]];
    cnt[f[p]]+=add;
    nw+=cnt[f[p]]*cnt[f[p]];
}
int gcd(int a,int b){
	return (a%b==0)?b:gcd(b,a%b);
}
void solve(){
	for(int i=1,l=1,r=0;i<=m;i++){
        while(r<ans[i].r)  update(++r,1);
        while(r>ans[i].r)  update(r--,-1);
        while(l<ans[i].l)  update(l++,-1);
        while(l>ans[i].l)  update(--l,1);
		if(ans[i].l==ans[i].r){
			ans[i].a=0;
			ans[i].b=1;
			continue;
		}
		ans[i].a=nw-(r-l+1),ans[i].b=(r-l+1)*(r-l);
		long long C=gcd(ans[i].a,ans[i].b);
		ans[i].a/=C;ans[i].b/=C;
	}
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&f[i]);
	}
	base=sqrt(n);
	for(int i=1;i<=n;i++){
		pos[i]=(i-1)/base+1;
	}
	for(int i=1;i<=m;i++){
		scanf("%lld%lld",&ans[i].l,&ans[i].r);
		ans[i].id=i;
	}
	sort(ans+1,ans+m+1,cmp1);
	solve();
	sort(ans+1,ans+m+1,cmp2);
	for(int i=1;i<=m;i++){
		cout<<ans[i].a<<"/"<<ans[i].b<<"\n";
	}
}
例2:小B的询问

方法同例 1 1 1,代码略。

例3:Mato的文件管理

首先简化题意:给定一个序列,求 [ L , R ] [L,R] [L,R]间的逆序对对数。

有了简化题意,问题就非常好处理了,我们用权值树状数组求逆序对,每次扩区间时将这个数的权值增加 1 1 1,缩区间时减少 1 1 1,顺便加减对应的贡献即可。

Code
#include<bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
int Read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')  f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
struct que{
	int l,r,id;
}q[50005];
int n,m,a[100005],b[100005],base,belong[100005],L[1005],R[1005],cnt=0;
int c[100005],ans[100005],ct;
bool operator < (que x,que y){
	if(belong[x.l]!=belong[y.l])  return belong[x.l]<belong[y.l];
	return x.r<y.r;
}
void Add(int x,int p){
	while(x<=n){
		c[x]+=p;
		x+=lowbit(x);
	}
}
int query(int x){
	int res=0;
	while(x>=1){
		res+=c[x];
		x-=lowbit(x);
	}
	return res;
}
void solve(){
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(q[i].l<l)
		  l--,Add(a[l],1),ct+=query(a[l]-1);
		while(q[i].l>l)
		  Add(a[l],-1),ct-=query(a[l]-1),l++;
		while(q[i].r<r)
		  Add(a[r],-1),ct-=r-l-query(a[r]),r--;
		while(q[i].r>r)
		  r++,Add(a[r],1),ct+=r-l+1-query(a[r]);
		ans[q[i].id]=ct;
	}
	for(int i=1;i<=m;i++)  cout<<ans[i]<<endl;
}
int main(){
	n=Read();base=sqrt(n);
	for(int i=1;i<=n;i++)  a[i]=Read(),b[i]=a[i];
	sort(b+1,b+n+1);
	int Q=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++)  a[i]=lower_bound(b+1,b+Q+1,a[i])-b;
	m=Read();
	for(int i=1;i<=m;i++){
		q[i].l=Read();q[i].r=Read();
		q[i].id=i;
	}
	for(int i=1;i<=n;i+=base){
		L[++cnt]=i;R[cnt]=i+base-1;
	}
	R[cnt]=n;
	for(int i=1;i<=cnt;i++){
		for(int j=L[i];j<=R[i];j++){
			belong[j]=i;
		}
	}
	sort(q+1,q+m+1);
	solve();
	return 0;
}

例4:Gty的二逼妹子序列
法1 ( O ( n n log ⁡ n ) ) (O(n\sqrt n\log n)) (O(nn logn))

将区间 [ a , b ] [a,b] [a,b]拆为 [ 1 , a − 1 ] [1,a-1] [1,a1] [ 1 , b ] [1,b] [1,b],新加入一个数 x x x时用线段树等数据结构将 [ x , b ] [x,b] [x,b]的值全部 + 1 +1 +1,查询时统计即可。

法2: ( O ( n n ) ) (O(n\sqrt n)) (O(nn ))

用值域分块,写法还是同HH的项链一样,记录每个数是否出现,出现了就 O ( 1 ) O(1) O(1)修改块内的数与该块出现的总种类数,最后在 O ( n ) O(\sqrt n) O(n )统计即可。

Code
#include<bits/stdc++.h>
using namespace std;
int Read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')  f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,a[100005],base,L[1005],R[1005],belong[100005],cnt=0,ans[1000005];
int sum[100005],s[100005],ss[100005];
struct que{
	int l,r,a,b,id;
}q[1000005];
bool operator < (que x,que y){
	if(belong[x.l]!=belong[y.l])  return belong[x.l]<belong[y.l];
	return x.r<y.r;
}
void Del(int pos){
	s[a[pos]]--;
	if(s[a[pos]]==0)  sum[belong[a[pos]]]--,ss[a[pos]]=0;
}
void Ins(int pos){
	s[a[pos]]++;
	if(s[a[pos]]==1)  sum[belong[a[pos]]]++,ss[a[pos]]=1;
}
int calc(int l,int r){
	int l1=belong[l],r1=belong[r],res=0;
	for(int i=l1+1;i<=r1-1;i++){
		res+=sum[i];
	}
	for(int i=l;i<=min(R[l1],r);i++){
		res+=ss[i];
	}
	if(r<=R[l1])  return res;
	for(int i=L[r1];i<=r;i++)  res+=ss[i];
	return res;
}
void solve(){
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(q[i].l>l)  Del(l++);
		while(q[i].l<l)  Ins(--l);
		while(q[i].r<r)  Del(r--);
		while(q[i].r>r)  Ins(++r);
		ans[q[i].id]=calc(q[i].a,q[i].b);
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<endl;
	}
}
int main(){
	n=Read(),m=Read();
	for(int i=1;i<=n;i++)  a[i]=Read();
	for(int i=1;i<=m;i++){
		q[i].l=Read(),q[i].r=Read();
		q[i].a=Read(),q[i].b=Read();
		q[i].id=i;
	}
	base=sqrt(n);
	for(int i=1;i<=n;i+=base){
		L[++cnt]=i,R[cnt]=i+base-1;
	}
	R[cnt]=n;
	for(int i=1;i<=cnt;i++){
		for(int j=L[i];j<=R[i];j++){
			belong[j]=i;
		}
	}
	sort(q+1,q+m+1);
	solve();
	return 0;
}
例5:[HNOI2016]序列

假设我们已经得到了区间 [ L , R ] [L,R] [L,R]的答案,那么区间 [ L , R + 1 ] [L,R+1] [L,R+1]的答案就是增加区间 [ L , R + 1 ] , [ L + 1 , R + 1 ] , . . . , [ R , R + 1 ] [L,R+1],[L+1,R+1],...,[R,R+1] [L,R+1],[L+1,R+1],...,[R,R+1]的答案和 a [ R + 1 ] a[R+1] a[R+1]

那么我们记录每个数前面第一个小于它的数的位置(用单调栈求出) p r e [ i ] pre[i] pre[i],记 f [ i ] f[i] f[i]为右端点为 i i i,左端点在 [ 1 , i ) [1,i) [1,i)间的答案,那么我们有 f [ i ] = f [ p r e [ i ] ] + ( i − p r e [ i ] + 1 ) × a [ i ] f[i]=f[pre[i]]+(i-pre[i]+1)\times a[i] f[i]=f[pre[i]]+(ipre[i]+1)×a[i]

求出这些后,我们就可以开始解决我们的问题了。记区间最小值的位置为 p o s pos pos(用ST表做),那么区间 [ L , R ] [L,R] [L,R]的答案为
a n s = a [ R ] × ( R − p r e [ R ] + 1 ) + a [ p r e [ R ] ] × ( p r e [ R ] − p r e [ p r e [ R ] ] + 1 ) + . . . + a [ p o s ] ∗ ( p o s − L + 1 ) ans=a[R]\times(R-pre[R]+1)+a[pre[R]]\times(pre[R]-pre[pre[R]]+1)+...+a[pos]*(pos-L+1) ans=a[R]×(Rpre[R]+1)+a[pre[R]]×(pre[R]pre[pre[R]]+1)+...+a[pos](posL+1)
通过 f f f的计算方式,我们可以得知:
f [ R ] − f [ p o s ] = a [ R ] × ( R − p r e [ R ] + 1 ) + a [ p r e [ R ] ] × ( p r e [ R ] − p r e [ p r e [ R ] ] + 1 ) + . . . f[R]-f[pos]=a[R]\times(R-pre[R]+1)+a[pre[R]]\times(pre[R]-pre[pre[R]]+1)+... f[R]f[pos]=a[R]×(Rpre[R]+1)+a[pre[R]]×(pre[R]pre[pre[R]]+1)+...
由于必有一个数的 p r e = p o s pre=pos pre=pos,所以上式总是成立的。

那么由上面两式相减得
a n s = f [ R ] − f [ p o s ] + a [ p o s ] ∗ ( p o s − L + 1 ) ans=f[R]-f[pos]+a[pos]*(pos-L+1) ans=f[R]f[pos]+a[pos](posL+1)
预处理 f f f与ST表,即可在 O ( 1 ) O(1) O(1)时间内完成右边的插入,删除时减去插入的贡献即可。

左边的对称处理即可。

注意:这里的莫队需要先扩再缩,否则会出现玄学错误。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')  f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
int a[100005],ans[100005],minn[100005][20],n,m,pre[100005],suf[100005];
int base,belong[100005],L[1005],R[1005],cnt=0,s[100005],tp=0;
struct que{
	int l,r,id;
}q[100005];
bool operator < (que x,que y){
	if(belong[x.l]!=belong[y.l])  return belong[x.l]<belong[y.l];
	return x.r<y.r;
}
int my_min(int x,int y){
	return (a[x]<a[y])?x:y;
}
void ST(){
	for(int j=1;(1<<j)<=n;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			minn[i][j]=my_min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
		}
	}
}
int query(int l,int r){
	int k=log(r-l+1)/log(2);
	return my_min(minn[l][k],minn[r-(1<<k)+1][k]);
}
namespace s1{
	int f[100005];
	void prework(){
		for(int i=1;i<=n;i++){
			f[i]=f[pre[i]]+(i-pre[i])*a[i];
		}
	}
	int calc(int l,int r){
		int pos=query(l,r+1);
		return (pos-l+1)*a[pos]+f[r+1]-f[pos];
	}
}
namespace s2{
	int f[100005];
	void prework(){
		for(int i=n;i>=1;i--){
			f[i]=f[suf[i]]+(suf[i]-i)*a[i];
		}
	}
	int calc(int l,int r){
		int pos=query(l-1,r);
		return (r-pos+1)*a[pos]+f[l-1]-f[pos];
	}
}
int res=0;
void solve(){
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(r<q[i].r)  res+=s1::calc(l,r++);
		while(l>q[i].l)  res+=s2::calc(l--,r);
		while(r>q[i].r)  res-=s1::calc(l,--r);
		while(l<q[i].l)  res-=s2::calc(++l,r);
		ans[q[i].id]=res;
	}
	for(int i=1;i<=m;i++)  printf("%lld\n",ans[i]);
}
signed main(){
	n=Read(),m=Read();
	for(int i=1;i<=n;i++){
		a[i]=Read();
		minn[i][0]=i;
	}
	ST();
	for(int i=1;i<=n;i++){
		while(tp&&a[s[tp]]>a[i])  suf[s[tp--]]=i;
		pre[i]=s[tp],s[++tp]=i;
	}
	while(tp)  pre[s[tp]]=s[tp-1],suf[s[tp--]]=n+1;
	s1::prework();
	s2::prework();
	base=sqrt(n);
	for(int i=1;i<=n;i+=base){
		L[++cnt]=i,R[cnt]=i+base-1;
	}
	R[cnt]=n;
	for(int i=1;i<=cnt;i++){
		for(int j=L[i];j<=R[i];j++){
			belong[j]=i;
		}
	}
	for(int i=1;i<=m;i++){
		q[i].l=Read(),q[i].r=Read();
		q[i].id=i;
	}
	sort(q+1,q+m+1);
	solve();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值