西安多校集训-分块学习笔记
前言
因bw用小脑想出来的课表,在 c层 根本学不到新知识,只能自学了qwq。
1.分块思想
优雅的暴力,优雅的常数,优雅的思想。
对于一个序列,我们可以对其分成若干段等长的序列,每段序列称为一块。
然后对序列做操作时,可以对整块序列统一处理(就是操作的左右端点包含这一块序列),对散块序列暴力修改(就是操作的序列与这一块序列相交但不包含)。
2.分块复杂度
由上述可知,我们令原序列长度为 \(n\) ,块长为 \(len\) ,那么分块复杂度为 \(O(\frac{n}{len}+len)\)。由(高中的数学知识?)常识可知,当 \(len=\sqrt n\) 时 \(\frac{n}{len}+len\) 最小,所以块长选取 \(\sqrt n\)时,理论复杂度最优为 \(O(n\sqrt n)\) 。当然出题人可能会特意的卡一卡。
3.分块的实现
①. 预处理
需要三个数组,\(L_x , R_x , belong_i\) 分别表示,第 \(x\) 块序列的左端点,右端点,以及原序列中的第 \(i\) 个点属于哪一块。
具体代码如下:
int n,m;
int len,tot,L[N],r[N],belong[N];
void build(){
len=sqrt(n);
tot=n/len;
if(n%len) tot++;
for(int i=1;i<=n;i++){
belong[i]=(i-1)/len+1;
}
for(int i=1;i<=tot;i++){
L[i]=(i-1)*len+1;
R[i]=min(i*len,n);
}
return ;
}
②. 区间加法
顺应上述的分块思想,对于整块的修改打上 tag ,对于散块就暴力修改。可能还用到了一点点的标记永久化的思想?
void modify(int l,int r,int w){
int x=belong[l],y=belong[r];
if(x==y){
for(int i=l;i<=r;i++){
a[i]+=w;
sum[x]+=w;
}
return ;
}else{
for(int i=l;i<=R[x];i++){
a[i]+=w;
sum[x]+=w;
}
for(int i=L[y];i<=r;i++){
a[i]+=w;
sum[y]+=w;
}
for(int i=x+1;i<y;i++){
tag[i]+=w;
sum[i]+=(R[i]-L[i]+1)*w;
}
return ;
}
}
③. 区间求和
区间操作都很类似,只要注意一下计算时是否需要加上 tag 就行。
int query(int l,int r){
int x=belong[l],y=belong[r];
int ans=0;
if(x==y){
for(int i=l;i<=r;i++){
ans+=a[i]+tag[x];
}
return ans;
}else{
for(int i=l;i<=R[x];i++){
ans+=a[i]+tag[x];
}
for(int i=L[y];i<=r;i++){
ans+=a[i]+tag[y];
}
for(int i=x+1;i<y;i++){
ans+=sum[i];
}
return ans;
}
}
fun fact:将上述代码拼起来就得到了 【模板】线段树 1 的代码,而且跑的比线段树快。(大雾)
4.区间多种运算
比如【模板】线段树 2。
同线段树一样,依据 板1 改一改就行啦。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
const int M=1e6+10;
const int p=571373;
int n,m;
int a[N],tag[N],mul[N],sum[N];
int len,tot,L[N],R[N],belong[N];
void build(){
len=sqrt(n);
tot=n/len;
if(n%len) tot++;
for(int i=1;i<=n;i++){
belong[i]=(i-1)/len+1;
mul[belong[i]]=1;
sum[belong[i]]+=a[i];
}
for(int i=1;i<=tot;i++){
L[i]=(i-1)*len+1;
R[i]=min(i*len,n);
}
return ;
}
void pushup(int pos){
for(int i=L[pos];i<=R[pos];i++){
a[i]=(a[i]*mul[pos]+tag[pos])%p;
}
mul[pos]=1;tag[pos]=0;
return ;
}
void modify(int l,int r,int w){
int x=belong[l],y=belong[r];
if(x==y){
for(int i=l;i<=r;i++){
a[i]=(a[i]%p+w%p)%p;
sum[x]=(sum[x]%p+w%p)%p;
}
return ;
}else{
pushup(x);
for(int i=l;i<=R[x];i++){
a[i]=(a[i]%p+w%p)%p;
sum[x]=(sum[x]%p+w%p)%p;
}
pushup(y);
for(int i=L[y];i<=r;i++){
a[i]=(a[i]%p+w%p)%p;
sum[y]=(sum[y]%p+w%p)%p;
}
for(int i=x+1;i<y;i++){
tag[i]=(tag[i]%p+w%p)%p;
sum[i]=(sum[i]+(R[i]-L[i]+1)%p*w%p)%p;
}
return ;
}
}
void times(int l,int r,int w){
int x=belong[l],y=belong[r];
if(x==y){
pushup(x);
for(int i=l;i<=r;i++){
a[i]=a[i]%p*w%p;
sum[x]=(sum[x]%p+a[i]%p*(w-1)%p)%p;
}
return ;
}else{
pushup(x);
for(int i=l;i<=R[x];i++){
sum[x]=(sum[x]%p+a[i]%p*(w-1)%p)%p;
a[i]=a[i]%p*w%p;
}
pushup(y);
for(int i=L[y];i<=r;i++){
sum[y]=(sum[y]%p+a[i]%p*(w-1)%p)%p;
a[i]=a[i]%p*w%p;
}
for(int i=x+1;i<y;i++){
tag[i]=tag[i]%p*w%p;
mul[i]=mul[i]%p*w%p;
sum[i]=sum[i]%p*w%p;
}
}
return ;
}
int query(int l,int r){
int x=belong[l],y=belong[r];
int ans=0;
if(x==y){
for(int i=l;i<=r;i++){
ans=(ans%p+a[i]%p*mul[x]%p+tag[x]%p)%p;
}
return ans%p;
}else{
for(int i=l;i<=R[x];i++){
ans=(ans%p+a[i]%p*mul[x]%p+tag[x]%p)%p;
}
for(int i=L[y];i<=r;i++){
ans=(ans%p+a[i]%p*mul[y]%p+tag[y]%p)%p;
}
for(int i=x+1;i<y;i++){
ans=(ans%p+sum[i]%p)%p;
}
return ans%p;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int mod;
cin>>n>>m>>mod;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build();
for(int i=1;i<=m;i++){
int op,l,r;
cin>>op>>l>>r;
if(op==2){
int k;
cin>>k;
modify(l,r,k);
}else if(op==3){
cout<<query(l,r)%p<<'\n';
}else{
int k;
cin>>k;
times(l,r,k);
}
}
return 0;
}
5. 区间其他操作
①. 教主的魔法
说人话:支持区间加法,查询区间内大于等于 k 的数的个数。
对于每一块都排好序,然后散块直接做就行了,对于整块,去二分查找后加上 \(R_i-x+1\) 即可。
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e6+50;
int n,m;
int a[N],tag[N],rnk[N];
int L[N],R[N],len,tot;
int belong[N];
void segsort(int k){
for(int i=L[k];i<=R[k];i++){
rnk[i]=a[i];
}
sort(rnk+L[k],rnk+R[k]+1);
}
void modify(int l,int r,int w){
int x=belong[l],y=belong[r];
if(x==y){
for(int i=l;i<=r;i++){
a[i]+=w;
}
segsort(x);
return ;
}
for(int i=l;i<=R[x];i++){
a[i]+=w;
}
segsort(x);
for(int i=L[y];i<=r;i++){
a[i]+=w;
}
segsort(y);
for(int i=x+1;i<y;i++){
tag[i]+=w;
}
return ;
}
int query(int l,int r,int c){
int ans=0,x=belong[l],y=belong[r];
if(x==y){
for(int i=l;i<=r;i++){
if(a[i]+tag[x]>=c) ans++;
}
return ans;
}
for(int i=l;i<=R[x];i++){
if(a[i]+tag[x]>=c) ans++;
}
for(int i=L[y];i<=r;i++){
if(a[i]+tag[y]>=c) ans++;
}
for(int i=x+1;i<y;i++){
ans+=R[i]-(lower_bound(rnk+L[i],rnk+R[i]+1,c-tag[i])-rnk)+1;
}
return ans;
}
void build(){
len=sqrt(n);
tot=n/len;
if(n%len) tot++;
for(int i=1;i<=n;i++){
belong[i]=(i-1)/len+1;
}
for(int i=1;i<=tot;i++){
L[i]=(i-1)*len+1;
R[i]=min(i*len,n);
sort(rnk+L[i],rnk+R[i]+1);
}
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
rnk[i]=a[i];
}
build();
for(int i=1;i<=m;i++){
char op;
int l,r,c;
cin>>op>>l>>r>>c;
if(op=='A'){
cout<<query(l,r,c)<<'\n';
}else{
modify(l,r,c);
}
}
return 0;
}
②. 由乃打扑克
第一道 Ynoi 题!
Ynoi 题都是人话,好评!
思路继承教主的魔法,整块内排序,考虑怎么求得第 k 小值。
去二分这个第 k 小值,然后查这个值是否是第 k 小就行啦。
#include<iostream>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
const int N=1e5+50;
const int inf=2e9+7;
int n,m;
int a[N],tag[N],rnk[N];
int len,tot;
int belong[N],L[N],R[N];
void build(){
len=120;
tot=n/len;
if(n%len) tot++;
for(int i=1;i<=n;i++){
belong[i]=(i-1)/len+1;
}
for(int i=1;i<=tot;i++){
L[i]=(i-1)*len+1;
R[i]=min(i*len,n);
sort(rnk+L[i],rnk+R[i]+1);
}
return ;
}
void Sort(int k){
for(int i=L[k];i<=R[k];i++) rnk[i]=a[i];
sort(rnk+L[k],rnk+R[k]+1);
return ;
}
void modify(int l,int r,int c){
int x=belong[l],y=belong[r];
if(x==y){
for(int i=l;i<=r;i++){
a[i]+=c;
}
Sort(x);
return ;
}
for(int i=l;i<=R[x];i++){
a[i]+=c;
}
Sort(x);
for(int i=L[y];i<=r;i++){
a[i]+=c;
}
Sort(y);
for(int i=x+1;i<y;i++){
tag[i]+=c;
}
return ;
}
int check(int l,int r,int w){
int x=belong[l],y=belong[r];
int cnt=0;
if(x==y){
for(int i=l;i<=r;i++){
if(a[i]+tag[x]<=w) cnt++;
}
return cnt;
}
for(int i=l;i<=R[x];i++){
if(a[i]+tag[x]<=w) cnt++;
}
for(int i=L[y];i<=r;i++){
if(a[i]+tag[y]<=w) cnt++;
}
for(int i=x+1;i<y;i++){
if(rnk[L[i]]+tag[i]>w) continue;
if(rnk[R[i]]+tag[i]<=w){
cnt+=R[i]-L[i]+1;
continue;
}
int ll=L[i],rr=R[i];
while(ll<rr){
int mid=((ll+rr)>>1)+1;
if(rnk[mid]+tag[i]<=w) ll=mid;
else rr=mid-1;
}
if(rnk[ll]+tag[i]<=w) cnt+=ll-L[i]+1;
}
return cnt;
}
int qmax(int l,int r){
int x=belong[l],y=belong[r];
int ans=-inf;
if(x==y){
for(int i=l;i<=r;i++){
ans=max(ans,a[i]+tag[x]);
}
return ans;
}
for(int i=l;i<=R[x];i++) ans=max(ans,a[i]+tag[x]);
for(int i=L[y];i<=r;i++) ans=max(ans,a[i]+tag[y]);
for(int i=x+1;i<y;i++){
ans=max(ans,rnk[R[i]]+tag[i]);
}
return ans;
}
int qmin(int l,int r){
int x=belong[l],y=belong[r];
int ans=inf;
if(x==y){
for(int i=l;i<=r;i++){
ans=min(ans,a[i]+tag[x]);
}
return ans;
}
for(int i=l;i<=R[x];i++) ans=min(ans,a[i]+tag[x]);
for(int i=L[y];i<=r;i++) ans=min(ans,a[i]+tag[y]);
for(int i=x+1;i<y;i++){
ans=min(ans,rnk[L[i]]+tag[i]);
}
return ans;
}
void query(int l,int r,int k){
if(k<1 || k>r-l+1){
cout<<-1<<'\n';
return ;
}
int ll=qmin(l,r),rr=qmax(l,r);
while(ll<=rr){
int mid=(ll+rr)>>1;
if(check(l,r,mid)<k){
ll=mid+1;
}else{
rr=mid-1;
}
}
cout<<ll<<'\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
rnk[i]=a[i];
}
build();
for(int i=1;i<=m;i++){
int op,l,r,k;
cin>>op>>l>>r>>k;
if(op==1){
query(l,r,k);
}else{
modify(l,r,k);
}
}
return 0;
}