首页 > 其他分享 >二次离线莫队笔记

二次离线莫队笔记

时间:2023-10-12 16:47:46浏览次数:43  
标签:int rep 离线 笔记 leq long 莫队 define

前言

莫队可以解决许多其他数据结构无法完成的问题,正在很多其他问题上也可以拿部分分甚至满分,只因其复杂度为小常数 \(O(n\sqrt n \times k)\)

其中 \(k\) 是单次扩张以及收缩的复杂度,而二离莫队可以在答案可差分的情况下达到 \(O(n\sqrt n + n \times k)\)

起源

lxl把二次离线莫队这个科技在洛谷的一场月赛里首次亮相

\(-->\)

例题 P4887 【模板】莫队二次离线(第十四分块(前体))

题面

珂朵莉给了你一个序列 \(a\),每次查询给一个区间 \([l,r]\),查询 \(l \leq i< j \leq r\),且 \(a_i \oplus a_j\) 的二进制表示下有 \(k\) 个 \(1\) 的二元组 \((i,j)\) 的个数。\(\oplus\) 是指按位异或。

对于100%的数据,\(1 \leq n, m \leq 100000\),\(0 \leq a_i, k < 16384\)。

思路

首先 \(k>14\) 肯定无解

考虑 \(k \leq 14\) 的情况

因为值域 \(V\) 很小,所以可以把二进制下有 \(k\) 个 \(1\) 数用桶装起来

而 \(a \bigotimes b = c\) 时有 \(a \bigotimes c = b\)

然后加入一个数 \(x\) 时直接把所有桶里的数与 \(x\) 的异或值出现次数 \(+1\)

但是暴力加的话复杂度会到 \(n \sqrt n \times C_{14}^{k}\)

考虑使用二离优化

设当前莫队指针为 \(l\) , \(r\)

\(r\) 要移动到 \(r'\) ,则答案变化量为 \([r+1,r']\) 对 \([l,r']\) 的贡献

差分转化成 \([r+1,r']\) 对 \([1,r']\) 减去 \([r+1,r']\) 对 \([1,l-1]\)

第一部分可以转化成区间 \([1,r']\) 的答案减去 \([1,r]\) 的答案

提前预处理 \([1,i]\) 区间的答案即可

第二部分我们离线扫描 \(l\) 即可

再看移动 \(l\)

考虑 \(l\) 移动到 \(l'\)

变化量是 \([l',l-1]\) 对 \([l',r]\) 的贡献

拆分成 \([l',l-1]\) 对 \([1,r]\) 的贡献减去 \([l',l-1]\) 对 \([1,l'-1]\) 的贡献

第二部分可以转化成 \([1,l]\) 答案减 \([1,l'-1]\) 的答案

另外的一起扫描即可

代码:

#include<bits/stdc++.h>
#define ll long long
#define SF scanf
#define PF printf
#define PB push_back
#define ull unsigned long long
#define R register
#define rep(i,x,y) for(R int i=x;i<=y;i++)
using namespace std;
int n,m,k,sq,a[100010],pos[100010];
ll sum[100010],ans[100010],t[20010],f[100010];
vector<int> d;
int Get(int x){
	int ret=0;
	for(;x>0;x-=(x&-x)) ret++;
	return ret;
}
struct node{
	int l,r,id;
}p[100010];
inline bool cmp(node x,node y){
	if(pos[x.l]!=pos[y.l]) return x.l<y.l;
	if(pos[x.l]&1) return x.r<y.r;
	return x.r>y.r;
}
struct qry{
	int l,r,p,i;
	friend bool operator <(qry x,qry y){
		return x.p<y.p;
	}
}q[200010];
int main(){
	SF("%d%d%d",&n,&m,&k);
	sq=sqrt(n);
	if(k>14){
		rep(i,1,m) puts("0");
		return 0;
	}
	rep(i,1,n) SF("%d",&a[i]),pos[i]=(i-1)/sq+1;
	rep(i,0,16383) if(Get(i)==k) d.PB(i);
	rep(i,1,m) SF("%d%d",&p[i].l,&p[i].r),p[i].id=i;
	rep(i,1,n){
		f[i]=t[a[i]]; f[i]+=f[i-1];
		for(int j:d) ++t[j^a[i]];
	} 
	sort(p+1,p+1+m,cmp);
	int l=1,r=0,s=0;
	rep(i,1,m){
		if(l<p[i].l) q[++s]={l,p[i].l-1,r,-i},sum[i]+=f[p[i].l-1]-f[l-1],l=p[i].l;
		if(r<p[i].r) q[++s]={r+1,p[i].r,l-1,-i},sum[i]+=f[p[i].r]-f[r],r=p[i].r;
		if(r>p[i].r) q[++s]={p[i].r+1,r,l-1,i},sum[i]-=f[r]-f[p[i].r],r=p[i].r;
		if(l>p[i].l) q[++s]={p[i].l,l-1,r,i},sum[i]-=f[l-1]-f[p[i].l-1],l=p[i].l;
	}
	sort(q+1,q+1+s);
	int np=0;
	rep(i,0,16383) t[i]=0;
	rep(i,1,s){
		while(np<q[i].p){
			++np;
			for(int j:d) ++t[j^a[np]];
		}
		rep(j,q[i].l,q[i].r) q[i].i>0?sum[q[i].i]+=(t[a[j]]-(j<=np&&!k)):sum[-q[i].i]-=(t[a[j]]-(j<=np&&!k));
	}
	rep(i,1,m) sum[i]+=sum[i-1],ans[p[i].id]=sum[i];
	rep(i,1,m) PF("%lld\n",ans[i]);
	return 0;
}

练习 P5398 [Ynoi2018] GOSICK

题意:

维多利加给了你一个序列 \(a\),每次询问给一个区间 \([l,r]\)。

查询 \(l \leq i,j\leq r\),且 \(a_i\) 是 \(a_j\) 倍数的二元组 \((i,j)\) 的个数。

记录一个 \(cnt\) 数组,其中 \(cnt[i]\) 代表 \(i\) 的倍数个数

不过单次修改时,一个数因数个数是 \(\sqrt V\) 的

先根号分治,前缀和 \(\leq \sqrt V\) 数的倍数个数

大于 \(\sqrt V\) 的跑二离莫队即可

时间复杂度 \(O(n \sqrt n +n\sqrt V)\)

具体细节看代码吧

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
#include<iostream>
#define ll long long
#define PB push_back
#define ull unsigned long long
#define R register
#define rep(i,x,y) for(R int i=x;i<=y;i++)
using namespace std;
const int MAXN=500000,V=500000,W=120;
int n,m,sq,a[MAXN+10],num1[MAXN+10],num2[MAXN+10],bl[MAXN+10],cnt[V+10],cnt1[V+10],cnt2[V+10];
ll f[MAXN+10],sum[MAXN+10],ans[MAXN+10];
vector<int> F[V+10];
inline int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-'0'),ch=getchar();
	return x;
}
struct node{
	int l,r,id;
}q[MAXN+10];
inline bool tmp1(node x,node y){
	if(bl[x.l]!=bl[y.l]) return x.l<y.l;
	if(bl[x.l]&1) return x.r<y.r;
	return x.r>y.r;
}
struct qry{
	int l,r,p,id;
}Q[MAXN*2+10];
inline bool tmp2(qry x,qry y){
	return x.p<y.p;
}
int main(){
//	freopen("P5398.in","r",stdin);
//	freopen("P5398_1.out","w",stdout);
	n=read(),m=read(); sq=m/sqrt(n);
	rep(i,1,n) a[i]=read(),bl[i]=(i-1)/sq+1;
	rep(i,1,m) q[i]={read(),read(),i};
	sort(q+1,q+1+m,tmp1);
	rep(i,1,n){
		if(!F[a[i]].size()){
			for(R int j=1;j*j<=a[i];j++){
				if(a[i]%j==0){
					num1[j]++;
					f[i]+=cnt[j]; F[a[i]].PB(j);
					if(j*j!=a[i]) num1[a[i]/j]++,f[i]+=cnt[a[i]/j];
				}
			}
		}else{
			for(R int j:F[a[i]]){
				num1[j]++;
				f[i]+=cnt[j];
				if(j*j!=a[i]) num1[a[i]/j]++,f[i]+=cnt[a[i]/j];			
			}
		}
		f[i]+=f[i-1]+num1[a[i]];
	//	cout<<f[i]<<'\n';
		cnt[a[i]]++;
	}
	int l=1,r=0,s=0;
	rep(i,1,m){
		if(r<q[i].r){
			Q[++s]={r+1,q[i].r,l-1,-i};
			sum[i]+=f[q[i].r]-f[r];
			r=q[i].r;
		}
		if(r>q[i].r){
			Q[++s]={q[i].r+1,r,l-1,i};
			sum[i]-=f[r]-f[q[i].r];
			r=q[i].r;
		}
		if(l>q[i].l){
			Q[++s]={q[i].l,l-1,r,i};
			sum[i]-=f[l-1]-f[q[i].l-1];
			l=q[i].l;
		}
		if(l<q[i].l){
			Q[++s]={l,q[i].l-1,r,-i};
			sum[i]+=f[q[i].l-1]-f[l-1];
			l=q[i].l;
		}
	//	sum[i]+=q[i].r-q[i].l+1;
	}
	int nowp=0;
	sort(Q+1,Q+1+s,tmp2);
	rep(i,1,s){
		while(nowp<Q[i].p){
			++nowp; 
			for(R int j:F[a[nowp]]) num2[j]++,num2[a[nowp]/j]+=(j*j!=a[nowp]);
			if(a[nowp]>W) for(R int j=1;j*a[nowp]<=V;j++) num2[j*a[nowp]]++;
		}
		rep(j,Q[i].l,Q[i].r) Q[i].id<0?sum[-Q[i].id]-=num2[a[j]]:sum[Q[i].id]+=num2[a[j]];
	}
//	rep(i,1,m)	cout<<l<<' '<<r<<' '<<sum[i]<<endl;
	rep(i,1,W){
		rep(j,1,n){
			cnt1[j]=cnt1[j-1]+(a[j]==i);
			cnt2[j]=cnt2[j-1]+(a[j]%i==0);
		}
		rep(j,1,s){
			ll w=1ll*cnt1[Q[j].p]*(cnt2[Q[j].r]-cnt2[Q[j].l-1]);
			Q[j].id<0?sum[-Q[j].id]-=w:sum[Q[j].id]+=w;
		}
	}
	rep(i,1,m) sum[i]+=sum[i-1],ans[q[i].id]=sum[i];
	rep(i,1,m) printf("%lld",ans[i]),putchar('\n');
	return 0;
} 

常数?挺小的

\[\large End \]

标签:int,rep,离线,笔记,leq,long,莫队,define
From: https://www.cnblogs.com/yzq-yzq/p/17759827.html

相关文章

  • 网络流笔记
    前言粗略地讲一下吧,大概能理解就行理论部分借鉴了oi-wiki,有问题欢迎指出网络流网络是一个特殊有向图$G=(V,E)$,特殊在于有源点$s$和汇点$t$首先网络流图中每条边$(u,v)$都有一个容量$c(u,v)$介绍流函数$f(u,v)$,指$u$到$v$流量所以就会有流量守恒,即$0\leq......
  • 在线莫队
    我竟然独立发现了这个东西...考虑普通的莫队就是让区间加元素操作尽可能少,那么对于所谓的在线莫队,我们可以先跑出一些标准区间的值,然后在对他进行拓展,最终得出结果。我们均匀的取出\(B\)个关键点,然后对于两两关键点算出答案。那么我们拓展区间时,最多要拓展\(O(\frac{n^2}{B})......
  • 《信息安全系统设计与实现》第六周学习笔记
    一、课程内容第十一章学习EXT2文件数据结构1、通过mkfs创建虚拟磁盘mke2fs[-bblksize-Nninodes]devicenblocks虚拟磁盘布局:2、操作系统内核中的文件系统函数3、系统调用4、I/O库函数5、用户命令6、sh脚本低级别的文件操作中的常用函数:打开和关闭文件:open():打......
  • DR7808 配置笔记
    CSA部分:内部CSA可以配置为单向,或者双向,一共有两个CSA,内部CSA的GAIN可以配置,挡位有10,20,40,80四种增益选项。也可以直接关闭内部CSA,CSA的过流保护值和过流保护滤波时间都可以单独设置。相关寄存器:DR7808_GENCTRL1DR7808_HBIDIAGDR7808_GENCTRL2DR7808_CSA_OC_SH寄存器相关描......
  • C#学习笔记--面向对象三大特征
    C#核心面向对象--封装用程序来抽象现实世界,(万物皆对象)来编程实现功能。三大特性:封装、继承、多态。类与对象声明位置:namespace中样式:class类名{}命名:帕斯卡命名法(首字母大写)实例化对象:根据类来新建一个对象。Personp=newPerson();成员变量声明在类语句块中用来描述......
  • 【刷题笔记】80. Remove Duplicates from Sorted Array II
    题目Givenasortedarraynums,removetheduplicatesin-placesuchthatduplicatesappearedatmosttwiceandreturnthenewlength.Donotallocateextraspaceforanotherarray,youmustdothisbymodifyingtheinputarrayin-placewithO(1)extramemor......
  • 开发者笔记 C++11新特性并发编程future
    上一篇介绍了<thread>文件里线程相关类,这篇将介绍C++<future>头文件里线程类,future里包含的类主要是处理异步任务,线程函数封装,线程间通信,同步,捕捉异常处理https://zhuanlan.zhihu.com/p/509118687future的引入c++11引入的future是为了解决异步通信问题的。future可以看做是数......
  • 2023_10_12_MYSQL_DAY_04_笔记
    2023_10_12_MYSQL_DAY_04_笔记14章课后作业CREATETABLExi(xidINTPRIMARYKEYAUTO_INCREMENT,xnameVARCHAR(10)UNIQUE,xheadVARCHAR(10)NOTNULL,xlocVARCHAR(30)DEFAULT'浑南区');CREATETABLEclass02(cnoINTPRIMARYKEY......
  • destoon开发笔记-调取资讯标题图
    今天也没做什么,就帮一个朋友调试了DT内核开发的模板,调取资讯内容第一张图作为标题图,网上也有一些教程,感觉不太好,所以我就写详细一些,希望对大家有帮助! 第一步:修改module\article\admin\template\edit.tpl.php     1<inputname="post[thumb_no]" type="te......
  • python 基础笔记-函数
    函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段·。   好处为: 一可以把程序中相对独立的功能模块抽取出来,减少重读代码的编写; 二是将来可以以重复的使用这些功能模块https://www.clw9335.com/zx/index-htm-page-5.html  定义一个函数 你可以定义一......