数列分块入门

news2024/11/26 14:55:39

本期是数列分块入门。其中的大部分题目来自hzwer在LOJ上提供的数列分块入门系列。

Blog:here  (其实是对之前分块的 blog 的整理补充)     sto   hzwer   orz     %%%            [转载]

------------------------------------------------------------------------------------------------------------------------

分块

我举个例子来说分块。

在一个学校里,有很多班级,而每一个班级就是一个块。

假设某天校长想知道一个班考试的总分,直接查询即可。那如果要查询 1 班的 30 号到 10 班的 20 号呢?对于完整的班级,直接查询;不完整的暴力。

那什么时候这个算法时间复杂度最低呢?答:当块的长度为\sqrt n时。

而这就是分块。

例题

LOJ-P6277:

我们每m个元素个元素分为一块,共有\frac{n}{m}块,以及区间两侧的两个不完整的块。这两个不完整的块中至多2m个元素。我们给每个块设置一个tag(就是记录这个块中元素一起加了多少),每次操作对每个整块直接\Theta (1)标记,而不完整的块元素较少,暴力修改元素的值。

这样,每次询问时返回元素的值加上其所在块的加法标记即可。

时间复杂度\Theta (\frac{n}{m})+\Theta (m)。根据均值不等式,当m\sqrt{n}时总复杂度最低。

#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
int a[maxn],idx[maxn],tag[maxn],tot;
void change(int l,int r,int c){
    for(int i=l;i<=min(idx[l]*tot,r);i++)
        a[i]+=c;
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++)
            a[i]+=c;
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        tag[i]+=c;
}
int main(){
	int n;
	cin>>n;
	tot=sqrt(n);
    for(int i=1;i<=n;i++)
		cin>>a[i];
    for(int i=1;i<=n;i++)
		idx[i]=(i-1)/tot+1;
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        cin>>opt>>l>>r>>c;
        if(opt==0)
			change(l,r,c);
        if(opt==1)
			cout<<a[r]+tag[idx[r]]<<endl;
    }
    return O;
}

LOJ-P6278:

我们先来思考只有询问操作的情况,不完整的块枚举统计即可;而要在每个整块内寻找小于一个值的元素数,于是我们不得不要求块内元素是有序的,这样就能使用二分法对块内查询,需要预处理时每块做一遍排序,复杂度\Theta (n\: log\: n),每次查询在\sqrt{n}个块内二分,以及暴力2 \sqrt{n}个元素,总复杂度\Theta (n\: log\: n+n\sqrt{n}\:\: log\: \sqrt{n})

那么区间加怎么办呢?套用第一题的方法,维护一个加法标记,略有区别的地方在于,不完整的块修改后可能会使得该块内数字乱序,所以头尾两个不完整块需要重新排序。在加法标记下的询问操作,块外还是暴力,查询小于(x-tag)的元素个数,块内用(x-tag)作为二分的值即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
int a[maxn],idx[maxn],tag[maxn],tot,n;
vector<int> block[505];
void reset(int x){
    block[x].clear();
    for(int i=(x-1)*tot+1;i<=min(x*tot,n);i++)
        block[x].push_back(a[i]);
    sort(block[x].begin(),block[x].end());
}
void change(int l,int r,int c){
    for(int i=l;i<=min(idx[l]*tot,r);i++)
        a[i]+=c;
    reset(idx[l]);
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++)
            a[i]+=c;
        reset(idx[r]);
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        tag[i]+=c;
}
int query(int l,int r,int c){
    int ans=0;
    for(int i=l;i<=min(idx[l]*tot,r);i++){
        if(a[i]+tag[idx[l]]<c)
			ans++;
	}
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++){
            if(a[i]+tag[idx[r]]<c)
				ans++;
		}
	}
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        ans+=lower_bound(block[i].begin(),block[i].end(),c-tag[i])-block[i].begin();
    return ans;
}
int main(){
	cin>>n;
	tot=sqrt(n);
    for(int i=1;i<=n;i++)
    	cin>>a[i];
    for(int i=1;i<=n;i++){
        idx[i]=(i-1)/tot+1;
        block[idx[i]].push_back(a[i]);
    }
    for(int i=1;i<=idx[n];i++)
        sort(block[i].begin(),block[i].end());
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        cin>>opt>>l>>r>>c;
        if(opt==0)
			change(l,r,c);
        if(opt==1)
			cout<<query(l,r,c*c)<<endl;
    }
    return O;
}

LOJ-P6279:

接着第二题的解法,其实只要把块内查询的二分稍作修改即可。

不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个set,这样如果还有插入、删除元素的操作,会更加的方便。

#include <bits/stdc++.h>
using namespace std;
const int maxn=10000S;
int a[maxn],idx[maxn],tag[maxn],tot=1000;
set<int> st[10S];
void change(int l,int r,int c){
    for(int i=l;i<=min(idx[l]*tot,r);i++){
        st[idx[l]].erase(a[i]);
        a[i]+=c;
        st[idx[l]].insert(a[i]);
    }
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++){
            st[idx[r]].erase(a[i]);
            a[i]+=c;
            st[idx[r]].insert(a[i]);
        }
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        tag[i]+=c;
}
int query(int l,int r,int c){
    int ans=-1;
    for(int i=l;i<=min(idx[l]*tot,r);i++){
        int val=a[i]+tag[idx[l]];
        if(val<c)
			ans=max(val,ans);
    }
    if(idx[l]!=idx[r]){     
        for(int i=(idx[r]-1)*tot+1;i<=r;i++){
            int val=a[i]+tag[idx[r]];
            if(val<c)
				ans=max(val,ans);
        }
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++){
        int x=c-tag[i];
        set<int>::iterator itr=st[i].lower_bound(x);
        if(itr==st[i].begin())
			continue;
        --itr;
        ans=max(ans,*itr+tag[i]);
    }
    return ans;
}
int main(){
	int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    	cin>>a[i]; 
    for(int i=1;i<=n;i++){
        idx[i]=(i-1)/tot+1;
        st[idx[i]].insert(a[i]);
    }
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        cin>>opt>>l>>r>>c;
        if(opt==0)
			change(l,r,c);
        if(opt==1)
			cout<<query(l,r,c)<<endl;
    }
    return 0;
}

LOJ-P6280:

这题的询问变成了区间上的询问,不完整的块还是暴力;而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。

考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。

#include <bits/stdc++.h>
using namespace std;
int idx[50005],tot;
long long a[50005],tag[50005],sum[50005];
void change(int l,int r,int c){
    for(int i=l;i<=min(idx[l]*tot,r);i++){
        a[i]+=c;
		sum[idx[l]]+=c;
	}
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++){
            a[i]+=c;
			sum[idx[r]]+=c;
		}
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        tag[i]+=c;
}
long long query(int l,int r){
    long long ans=0;
    for(int i=l;i<=min(idx[l]*tot,r);i++)
        ans+=a[i]+tag[idx[l]];
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++)
            ans+=a[i]+tag[idx[r]];
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        ans+=sum[i]+tot*tag[i];
    return ans;
}
int main(){
	int n;
    cin>>n;
	tot=sqrt(n);
    for(int i=1;i<=n;i++)
		cin>>a[i];
    for(int i=1;i<=n;i++){
        idx[i]=(i-1)/tot+1;
        sum[idx[i]]+=a[i];
    }
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        cin>>opt>>l>>r>>c; 
        if(opt==O)
			change(l,r,c);
        if(opt==1)
            cout<<query(l,r)%(c+1)<<endl;
    }
    return 0;
}

LOJ-P6281:

稍作思考可以发现,开方操作比较棘手,主要是对于整块开方时,必须要知道每一个元素,才能知道他们开方后的和,也就是说,难以快速对一个块信息进行更新。

看来我们要另辟蹊径。不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成0或者1

如果每次区间开方只不涉及完整的块,意味着不超过2\sqrt{n}个元素,直接暴力即可。

如果涉及了一些完整的块,这些块经过几次操作以后就会都变成01,于是我们采取一种分块优化的暴力做法,只要每个整块暴力开方后,记录一下元素是否都变成了01,区间修改时跳过那些全为01的块即可。

这样每个元素至多被开方不超过4次,显然复杂度没有问题。

#include <bits/stdc++.h> 
using namespace std;
int a[50005],sum[50005],idx[50005],tot;
bool flag[50005];
void solve(int x){
    if(flag[x])
		return;
    flag[x]=1;
    sum[x]=0;
    for(int i=(x-1)*tot+1;i<=x*tot;i++){
        a[i]=sqrt(a[i]);
		sum[x]+=a[i];
        if(a[i]>1)
			flag[x]=0;
    }
}
void change(int l,int r,int c){
    for(int i=l;i<=min(idx[l]*tot,r);i++){
        sum[idx[l]]-=a[i];
        a[i]=sqrt(a[i]);
        sum[idx[l]]+=a[i];
    }
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++){
            sum[idx[r]]-=a[i];
            a[i]=sqrt(a[i]);
            sum[idx[r]]+=a[i];
        }
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        solve(i);
}
int query(int l,int r){
    int ans=0;
    for(int i=l;i<=min(idx[l]*tot,r);i++)
        ans+=a[i];
    if(idx[l]!=idx[r]){
        for(int i=(idx[r]-1)*tot+1;i<=r;i++)
            ans+=a[i];
    }
    for(int i=idx[l]+1;i<=idx[r]-1;i++)
        ans+=sum[i];
    return ans;
}
int main(){
	int n;
    cin>>n;
	tot=sqrt(n);
    for(int i=1;i<=n;i++)
		cin>>a[i];
    for(int i=1;i<=n;i++){
        idx[i]=(i-1)/tot+1;
        sum[idx[i]]+=a[i];
    }
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        cin>>opt>>l>>r>>c;
        if(opt==0)
			change(l,r,c);
        if(opt==l)
            cout<<query(l,r)<<endl;
    }
    return 0;
}

LOJ-P6284:

区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。

模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。

我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就\Theta(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。

这样看似最差情况每次都会耗费\Theta(n)的时间,但其实可以这样分析:

假设初始序列都是同一个值,那么查询是\Theta(\sqrt n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是\Theta(\sqrt{n})。换句话说,要想让一个操作耗费\Theta(n)的时间,要先花费\sqrt{n}个操作对数列进行修改。初始序列不同值,经过类似分析后,就可以放心的暴力啦。

#include <bits/stdc++.h>
using namespace std;
int a[maxn],block[maxn],tag[maxn],n,s;
void reset(int x){
    if(tag[x]==-1)
		return;
    for(int i=(x-1)*s+1;i<=s*x;i++)
        a[i]=tag[x];
    tag[x]=-1;
}
int query(int l,int r,int c){    
    int ans=0;
    reset(block[l]);
    for(int i=l;i<=min(block[l]*s,r);i++){
        if(a[i]!=c)
			a[i]=c;
        else
			ans++;
	}
    if(block[l]!=block[r]){
        reset(block[r]);
        for(int i=(block[r]-1)*s+1;i<=r;i++){
            if(a[i]!=c)
				a[i]=c;
            else
				ans++;
		}
    }
    for(int i=block[l]+1;i<=block[r]-1;i++){
        if(tag[i]!=-1){
            if(tag[i]!=c)
				tag[i]=c;
            else
				ans+=s;
        }
        else{
            for(int j=(i-1)*s+1;j<=i*s;j++){
                if(a[j]!=c)
					a[j]=c;
                else
					ans++;
			}
            tag[i]=c;
        }
	}
    return ans;
}
int main(){
    memset(tag,-1,sizeof(tag));
    int n;
    cin>>n;
	s=sqrt(n);
    for(int i=1;i<=n;i++)
		cin>>a[i];
    for(int i=1;i<=n;i++)
		block[i]=(i-1)/s+1;
    for(int i=1;i<=n;i++){
        int l,r,c;
        cin>>l>>r>>c;
       	cout<<query(l,r,c)<<endl;
    }
    return 0;
}

HDU 5057:

分块板题。

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
int v[maxn][15],tag[320][15][15],a[maxn];
void update(int x,int y,int z){
    for(int d=1;d<=10;d++){
        v[x][d]=y%10;
        tag[x/S][d][y%10]+=z;
        y/=10;
    }
}
int query(int l,int r,int d,int p){
    int L=l/S,R=r/S,res=0;
    if(L==R){
        for(int i=l;i<=r;i++)
			res+=(v[i][d]==p);
    }
	else{
        for(int i=l;i<(L+1)*S;i++)
			res+=(v[i][d]==p);
        for(int i=R*S;i<=r;i++)
			res+=(v[i][d]==p);
        for(int i=L+1;i<R;i++)
			res+=tag[i][d][p];
    }
    return res;
}
int main(){
    int t;
    cin>>t;
    while(t--){
    	memset(tag,0,sizeof(tag));
    	memset(v,0,sizeof(v));
    	int n,m;
    	cin>>n>>m;
    	S=sqrt(n);
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    		update(i,a[i],1);
		}
		while(m--){
        	char op;
        	cin>>op;
        	if(op=='S'){
            	int x,y;
            	cin>>x>>y;
            	update(x,a[x],-1);
            	update(x,y,1);
            	a[x]=y;
        	}
			else{
            	int l,r,d,p;
            	cin>>l>>r>>d>>p;
            	cout<<query(l,r,d,p)<<endl;
        	}
    	}
	}
    return 0;
}

友情提醒:不要Ctrl C+Ctrl V

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2233943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot+Gpt个人健康管家管理系统【提供源码+答辩PPT+参考文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

苍穹外卖day-01

后端环境搭建 创建git仓库 提交代码 创建gitee远程仓库 开始连接远程仓库 运行sql文件&#xff0c;创建数据库。这里选取的可视化工具是navicat 编译一下项目 运行项目 登录的账号和密码在数据库中的emploee表中 退出前端界面登录后再重新登录&#xff0c;可以从后台清晰看到前…

【Docker故障处理】Ubuntu系统下tab键无法补全问题解决

【Docker故障处理】Ubuntu系统下tab键无法补全问题解决 一、环境介绍1.1 本地环境规划1.2 本次实践说明二、故障现象三、故障分析3.1 可能的原因3.2 排错思路四、故障处理4.1 安装bash-completion4.2 下载补全脚本4.3 配置永久生效五、测试tab键补全六、总结一、环境介绍 1.1 …

若依框架-添加测试类-最新

1、在【ruoyi-admin】的pom.xml下添加依赖 <!-- 单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId><scope>test</scope></dependency><dependency>…

CSS基础概念:什么是 CSS ? CSS 的组成

什么是 CSS&#xff1f; CSS&#xff08;层叠样式表&#xff0c;Cascading Style Sheets&#xff09;是一种用于控制网页外观的样式表语言。通过定义样式规则&#xff0c;CSS 可以指定 HTML 页面中各个元素的显示方式&#xff0c;包括颜色、布局、字体、间距等。 与 HTML 专注…

解密RFID技术提升应急消防管理效率的过程

一、部署RFID消防应急解决策略的具体步骤 &#xff08;1&#xff09;需求探讨与战略规划阶段 深入探究&#xff1a;全面、深刻地理解消防领域在资源分配、人员跟踪、应急救援等方面的实际需求。与消防机构紧密合作&#xff0c;共同确定RFID技术的应用提升和具体实施范围。 细…

国内短剧源码短剧系统搭建小程序部署H5、APP打造短剧平台

​在当今的互联网时代&#xff0c;短剧作为一种新兴的娱乐形式&#xff0c;受到了越来越多用户的喜爱。为了提供更好的用户体验和满足用户需求&#xff0c;一个好的短剧系统需要具备多元化的功能和优质的界面设计。 本文将介绍国内短剧源码短剧系统搭建小程序部署H5、APP所需的…

使用docker安装zlmediakit服务(zlm)

zlmediakit安装 zlmediakit安装需要依赖环境和系统配置&#xff0c;所以采用docker的方式来安装不容易出错。 docker pull拉取镜像(最新) docker pull zlmediakit/zlmediakit:master然后先运行起来 sudo docker run -d -p 1935:1935 -p 80:80 -p 8554:554 -p 10000:10000 -p …

qt QDragEnterEvent详解

1、概述 QDragEnterEvent是Qt框架中用于处理拖放进入事件的一个类。当用户将一个拖拽对象&#xff08;如文件、文本或其他数据&#xff09;拖动到支持拖放操作的窗口部件&#xff08;widget&#xff09;上时&#xff0c;系统会触发QDragEnterEvent事件。这个类允许开发者在拖拽…

HarmonyOS Next星河版笔记--界面开发(3)

属性 1.1.设计资源-svg图标 需求&#xff1a;界面中展示图标→可以使用的svg图标(任意放大缩小不失真、可以改变颜色) 使用方式&#xff1a; ①设计师提供&#xff1a;基于项目的图标&#xff0c;拷贝到项目目录使用 Image($r(app.media.ic_dianpu)) .width(40) fillColor…

查找连表的倒数第k个节点

居安思危 何解&#xff1f; 1、假如有1、2、3三个节点&#xff0c;找倒数第二个&#xff0c;实际是整数第几个&#xff1f; 3-21 2 &#xff1a; 及 length - k 1 ,所以先遍历找节点长度&#xff0c;在遍历找所需节点 // 今天这不是力扣的var findNode function(head , k){…

陪玩系统源码APP中的语音聊天直播房间有哪些功能?

陪玩系统源码APP通常采用Springboot、MybatisPlus和MySQL等后端技术栈来构建后端服务。这些技术提供了强大的数据处理能力和灵活的扩展性&#xff0c;能够满足高并发、低延迟的业务需求。 陪玩系统源码线上线下家政游戏陪玩前端开发框架如uniapp&#xff08;针对Web和小程序&am…

【python】OpenCV—findContours(4.3)

文章目录 1、功能描述2、代码实现3、完整代码4、结果展示5、涉及到的库函数5.1、cv2.Canny5.2 cv2.boxPoints 6、参考 1、功能描述 找出图片中的轮廓&#xff0c;拟合轮廓外接椭圆和外接矩阵 2、代码实现 导入必要的库&#xff0c;固定好随机种子 import cv2 as cv import …

介绍目标检测中mAP50和mAP50-95的区别

在目标检测任务中&#xff0c;mAP&#xff08;mean Average Precision&#xff09;是一个常用的性能评估指标&#xff0c;用于衡量模型在不同类别和不同IoU&#xff08;Intersection over Union&#xff09;阈值下的平均精度。mAP50和mAP50-95是mAP的两个特定版本&#xff0c;它…

三维测量与建模笔记 - 2.2 射影几何

教程中H矩阵写的有问题&#xff0c;上图中H矩阵应该是&#xff08;n1) x (m1) 共点不变性,下图中黄色方块标记的点&#xff0c;在射影变换前后&#xff0c;虽然直线的形状有所变化&#xff0c;但仍然相交于同一个点。 共线不变性&#xff0c;下图黄色标记的两个点&#xff0c;在…

【设计模式】策略模式定义及其实现代码示例

文章目录 一、策略模式1.1 策略模式的定义1.2 策略模式的参与者1.3 策略模式的优点1.4 策略模式的缺点1.5 策略模式的使用场景 二、策略模式简单实现2.1 案例描述2.2 实现代码 三、策略模式的代码优化3.1 优化思路3.2 抽象策略接口3.3 上下文3.4 具体策略实现类3.5 测试 参考资…

nuPlan最新SOTA,香港科技大学发布基于学习决策范围内的规划PlanScope

nuPlan最新SOTA&#xff0c;香港科技大学发布基于学习决策范围内的规划PlanScope Abstract 在自动驾驶的背景下&#xff0c;基于学习的方法在规划模块的开发中表现出了很大的潜力。在规划模块的训练过程中&#xff0c;直接最小化专家驾驶日志与规划输出之间的差异是一种广泛采…

String字符串 Random数字运算

Java API String 在使用String类进行字符串操作之前需要对String类进行初始化,在Java中可以通过以下两种方式对String类进行初始化 (1) 使用字符串常量 直接初始化一个String对象,具体代码如下 这是比较简化的写法 String a "abd"; (2) 使用String类的构造方法…

【Maven】——基础入门,插件安装、配置和简单使用,Maven如何设置国内源

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a; 一&#xff1a;Maven插件的安装 1&#xff1a;环境准备 2&#xff1a;创建项目 二…

王道408 DS 数据结构笔记

408 数据结构 文章目录 线性表顺序表静态分配动态分配算法设计 链表单链表双链表循环链表循环单链表循环双链表 静态链表算法设计 栈顺序栈共享栈链式栈算法设计应用 队列循环队列链队列算法设计 串顺序存储链式存储串的模式匹配 树二叉树线索二叉树树、森林树、森林的存储树和…