从疫情中的体温测量到分块思想的运用

Once upon a time,COVID-19席卷全球,Chinese Government要求学校复课时必须测量学生体温
YC中学有几万名同窗,要找到发烧的同窗进行隔离 若是要让一位老师完成全部测温任务,那这将是一个大工程,效率会很低(左图)
因此将学校全部同窗分红班级进行,而后汇总,效率会更高(右图)c++

在这里插入图片描述

刚刚中咱们说的:算法

将学校全部同窗分红班级进行,而后汇总数组

这就是一种分块
那问题来了,什么是分块呢?
其实经过刚刚的情景,你已经领悟到了分块的本质:函数

将一个总体划分为若干个小块,进行处理优化

算法中,与之对应的就是:spa

总体 小块
学校 班级
数组 若干元素

那么,分块究竟是怎么一种思想呢?
整块维护,残块查找code

仍是以测量体温举例:blog

如今YC中学要查找体温在36℃~37.5℃区间内的同窗
怎么作呢?
不可能又去挨个同窗去统计、去数吧
那就作一张大表吧,在以前测温的时候就把34 ~ 35℃、35 ~36℃、36 ~ 37℃、37 ~ 38℃、38 ~ 39℃......的同窗分别列出来,数量分别加出来
而后36 ~ 37℃能够就直接在表里查出人数
那37 ~ 37.5℃怎么办呢?
表内并无37 ~ 37.5℃的这样0.5大小的区间啊
那就在37 ~ 38℃这个区间去找呗
方法能够直接暴力遍历,也能够二分查找等等图片

刚刚解决的问题就是一个典型的分块
像34 ~ 35℃、35 ~36℃、36 ~ 37℃、37 ~ 38℃、38 ~ 39℃这种列在表上给出的就是整块
37 ~ 37.5℃这种表上没有,包含在一个其余整块中的但又不足一个整块的就叫作残块ip

不难发现,其实分块这个思想是一种暴力,一种优化的暴力,但每每也颇有效
Such as 线段树过于臃肿,代码冗长,大材小用;而直接暴力就会TLE,不能知足数据大小
这就很适合分块了
那么咱们具体怎么作呢?

咱们先要求得应该分为多少个区块嘛,而后求得每一个区块应该包含多少个元素
而后在输入时分块
要使状况最优,那么区块既不能太少也不能太多
太少,整块的数量会太少,花费大量的时间处理残块
太多,区块的长度会过短,失去总体处理区块的意义
因此,咱们取块数为根号n
而开平方开不尽的n,咱们一般在最后接一个不足整块元素的假整块(能够看作整块)
这样在最坏状况下
咱们要处理接近根号n整块,还要对长度为 2倍根号n 的残块最后单独处理

cin>>n;
	blo=sqrt(n);//sqrt()开平方函数
	for(int i=1;i<=n;i++){
		cin>>a[i];//储存元素a[i]
		pos[i]=(i-1)/blo+1;//pos[i]为记录元素a[i]属于第几个整块
		m[pos[i]]=max(a[i],m[pos[i]]);//寻找第pos[i]个整块的最大值存入m[pos[i]]
	}

咱们先统计左右残块,而后再统计整块

cin>>q;
	int l,r;
	while(q--){
		cin>>l>>r;
		l++;
		r++;
		int ans=0;
		for(int i=l;i<=min(r, pos[l]*blo);i++){//统计左残缺块 
			ans=max(ans,a[i]);
		} 
		if(pos[l]!=pos[r]){//存在右残缺块 
			for(int i=(pos[r]-1)*blo+1;i<=r;i++){//统计右残缺块 
				ans=max(ans,a[i]);
			}
		} 
		for( int i=pos[l]+1;i<=pos[r]-1;i++){//统计中间整块 
			ans=max(ans,m[i]);
		}	
		cout<<ans<<endl;
	}

分块入门之求最大值

先看一个例题

分块入门之求最大值
Input
第一行给出一个数字N,接下来N+1行,每行给出一个数字Ai,(1<=i<=N<=1E5)
接来给出一个数字Q(Q<=7000),表明有Q个询问
每组询问格式为a,b即询问从输入的第a个数到第b个数,其中的最大值是多少
Output
如题所述
Sample Input
10 0 1 2 3 2 3 4 3 2 1 0 5 0 10 2 4 3 7 7 9 8 8
Sample Output
4 3 4 3 2

模板题,而后刚刚已经讲过了这个代码
惟一的坑就在于接下来N+1行都是数字Ai
也就是有n+1数字Ai
也就是n须要n++

#include <bits/stdc++.h>
using namespace std;
int n;
int a[101000];
int q;
int blo;
int pos[101000];
int m[101000];
//blo为区间大小,pos[i]表示a[i]元素位于第pos[i]块,m[i]表示区块最大值
int main(){
	cin>>n;
	n++;
	blo=sqrt(n);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pos[i]=(i-1)/blo+1;
		m[pos[i]]=max(a[i],m[pos[i]]);
	}
	cin>>q;
	int l,r;
	while(q--){
		cin>>l>>r;
		l++;
		r++;
		int ans=0;
		for(int i=l;i<=min(r, pos[l]*blo);i++){//统计左残缺块 
			ans=max(ans,a[i]);
		} 
		if(pos[l]!=pos[r]){//存在右残缺块 
			for(int i=(pos[r]-1)*blo+1;i<=r;i++){//统计右残缺块 
				ans=max(ans,a[i]);
			}
		} 
		for( int i=pos[l]+1;i<=pos[r]-1;i++){//统计中间整块 
			ans=max(ans,m[i]);
		}	
		cout<<ans<<endl;
	}
	return 0;
}

教主的魔法

[Noip模拟题]教主的魔法
Description
教主最近学会了一种神奇的魔法,可以令人长高
因而他准备演示给XMYZ信息组每一个英雄看
因而N个英雄们又一次汇集在了一块儿
此次他们排成了一列,被编号为一、二、……、N
每一个人的身高一开始都是不超过1000的正整数
教主的魔法每次能够把闭区间[L, R](1≤L≤R≤N)内的英雄的身高所有加上一个整数W
(虽然L=R时并不符合区间的书写规范,但咱们能够认为是单独增长第L(R)个英雄的身高)
CYZ、光哥和ZJQ等人不信教主的邪
因而他们有时候会问WD闭区间 [L,R] 内有多少英雄身高大于等于C
以验证教主的魔法是否真的有效
WD巨懒,因而他 把这个回答的任务交给了你
Input
第1行为两个整数N、Q。Q为问题数与教主的施法数总和
第2行有N个正整数,第i个数表明第i个英雄的身高
第3到第Q+2行每行有一个操做:
(1)若第一个字母为"M",则紧接着有三个数字L、R、W
表示对闭区间 [L, R]内全部英雄的身高加上W
(2)若第一个字母为"A",则紧接着有三个数字L、R、C
询问闭区间 [L, R] 内有多少英雄的身高大于等于C
N≤1000000,Q≤3000,1≤W≤1000,1≤C≤1,000,000,000
Output
对每一个"A"询问输出一行,仅含一个整数,表示闭区间 [L, R] 内身高大于等于C的英雄数。Sample Input
5 3 1 2 3 4 5 A 1 5 4 M 3 5 1 A 1 5 4
Sample Output
2 3
【输入输出样例说明】
原先5个英雄身高为一、二、三、四、5,此时[1, 5]间有2个英雄的身高大于等于4
教主施法后变为一、二、四、五、6,此时[1, 5]间有3个英雄的身高大于等于4

不少元素,进行增长、查找最大值操做

多了一个修改操做,不是很难
同理像查找这样整块维护,残块增长
咱们就再增长一个数组,统一记录每一个整块变化量是多少
记录每一个整块的变化量,而后最后找最值的时候,单个整块的最值加上或者减去变化量比较就能够了
残块的单个元素直接加上或者减去变化量,找最值

void update(int x,int y,int v){
    if(pos[x]==pos[y]){
        for(int i=x;i<=y;i++)a[i]=a[i]+v;
    }
    else{
        for(int i=x;i<=pos[x]*block;i++)a[i]=a[i]+v;
        for(int i=(pos[y]-1)*block+1;i<=y;i++)a[i]=a[i]+v;
    }
    reset(pos[x]);reset(pos[y]);
    for(int i=pos[x]+1;i<pos[y];i++)
       add[i]+=v;
}

#include<bits/stdc++.h>
using namespace std;
int n;
int q,m,block;
int a[1010000],b[1010000],pos[1010000],add[1010000];
void reset(int x){
    int l=(x-1)*block+1,r=min(x*block,n);
    for(int i=l;i<=r;i++)
        b[i]=a[i];
    sort(b+l,b+r+1);
}
int find(int x,int v){
    int l=(x-1)*block+1,r=min(x*block,n);
    int last=r;
    while(l<=r){
        int mid=(l+r)>>1;
        if(b[mid]<v)l=mid+1;
        else r=mid-1;
    }
    return last-l+1;
}
void update(int x,int y,int v){
    if(pos[x]==pos[y]){
        for(int i=x;i<=y;i++)a[i]=a[i]+v;
    }
    else{
        for(int i=x;i<=pos[x]*block;i++)a[i]=a[i]+v;
        for(int i=(pos[y]-1)*block+1;i<=y;i++)a[i]=a[i]+v;
    }
    reset(pos[x]);reset(pos[y]);
    for(int i=pos[x]+1;i<pos[y];i++)
       add[i]+=v;
}
int query(int x,int y,int v){
    int sum=0;
    if(pos[x]==pos[y]){
        for(int i=x;i<=y;i++)if(a[i]+add[pos[i]]>=v)sum++;
    }
    else {
        for(int i=x;i<=pos[x]*block;i++)
            if(a[i]+add[pos[i]]>=v)sum++;
        for(int i=(pos[y]-1)*block+1;i<=y;i++)
            if(a[i]+add[pos[i]]>=v)sum++;
    }
    for(int i=pos[x]+1;i<pos[y];i++)
        sum+=find(i,v-add[i]);
    return sum;
}
int main(){
    cin>>n>>q;
    block=int(sqrt(n));
    for(int i=1;i<=n;i++){
        cin>>a[i];
        pos[i]=(i-1)/block+1;
        b[i]=a[i];
    }
    if(n%block)m=n/block+1;
    else m=n/block;
    for(int i=1;i<=m;i++)reset(i);
    for(int i=1;i<=q;i++){
        char ch[5];int x,y,v;
        cin>>ch>>x>>y>>v;
        if(ch[0]=='M'){
        	update(x,y,v);
		}else{
			cout<<query(x,y,v)<<endl;
		}
    }
    return 0;
}

END

相关文章
相关标签/搜索