根号算法学习记录
根号算法专题
分块基础
根号平衡
对于两个不同方面的复杂度,直接做的话一个很小,一个很大,我们用根号使得两者复杂度同阶级以降低总复杂度。这个叫根号平衡。
一个典型的应用是根号分治。打个比方我们想 以下复杂度统计序列从某一位下标等差的一种前缀和,我们全部预处理空间复杂度是 ,时间复杂度也是 的,这样做一次是 。直接暴力做求一次是 ( 是下标公差)。这个我们可以两者折中,预处理 的(做一次 ,预处理 ),暴力算 的(做一次 )。
后面我们的很多根号算法都是基于这个思想,即取一个值来平衡两个极端的复杂度。
是大致这么个思想,具体应用往下。
最为简单的分块
题目:维护区间加查询、区间加修改。
把序列分块,完整的块打标记,零散的块暴力进行。
设块长为 ,单次操作复杂度整块 ,零散块 。考虑根号平衡,取 ,总复杂度 。
稍微雷一点的分块
题目:维护区间加、区间查询排名
分块,然后维护块内排序后数组。修改时整块打标记,散块重构。查询每个块内排序数组上直接二分。
设块长为 ,
查询复杂度:整块 ,散块 。
修改复杂度:整块 ,散块 。(散块的重构用归并)
我们让查询和修改的整块与散块复杂度根号平衡,取块长 ,总复杂度 。
再看下面这个:
这里是上面的查询变为查询第 小,如果直接套用上面的套路,复杂度需要多一个二分答案的 ,会被卡掉,不行。
改块长,令 ,除了散块复杂度单次操作全都变为根号以下。因为查询外层套的二分,我们只需考虑优化查询的散块复杂度。先把两边散块临时合并,之后每次在上面二分即可。
总复杂度 。
注,我们实际取块长时并不直接取理论复杂度,而是类似手动模拟退火。
#include<bits/stdc++.h>
#define ll long long
#define db double
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
template<class T>
inline void read(T &s){
s=0;char ch=gc();bool f=0;
while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
if(ch=='.'){
T p=0.1;ch=gc();
while('0'<=ch&&ch<='9') {s=s+p*(ch^48);p/=10;ch=gc();}
}
s=f?-s:s;
}
template<class T,class ...A>
inline void read(T &s,A &...a){
read(s);read(a...);
}
};
using IO::read;
const int N=1e5+3;
const int B=328;
int n,m;
int a[N];
int bl[N];
struct Block{
inline int len(){
return ed-st+1;
}
int st,ed;
int el,er,ela;
int add;
struct node{
int x,p;
}ov[B+3];
}b[(N-3)/B+3];
//int tim2,t,tim1,tim3,tim4;
Block::node tmp1[N],tmp2[N];
inline void MergeSort(Block::node *ans,int n,int m){
int l=1,r=1;
for(int i=1;i<=n+m;++i){
if((l<=n and tmp1[l].x<tmp2[r].x) or r>m){
ans[i]=tmp1[l];++l;
}else{
ans[i]=tmp2[r];++r;
}
}
}
inline void modify(int l,int r,int k){
if(bl[l]==bl[r]){
//t=clock();
for(int i=l;i<=r;++i){
a[i]+=k;
}
int top1=0,top2=0;
for(int i=b[bl[l] ].st;i<=b[bl[l] ].ed;++i){
if(
l<=b[bl[l] ].ov[i-b[bl[l] ].st+1].p and
b[bl[l] ].ov[i-b[bl[l] ].st+1].p<=r
){
tmp1[++top1]=b[bl[l] ].ov[i-b[bl[l] ].st+1];
tmp1[top1].x+=k;
}else{
tmp2[++top2]=b[bl[l] ].ov[i-b[bl[l] ].st+1];
}
}
MergeSort(b[bl[l] ].ov,top1,top2);
//tim1+=clock()-t;
return;
}
//t=clock();
for(int i=bl[l]+1;i<=bl[r]-1;++i){
b[i].add+=k;
}
//tim2+=clock()-t;
//t=clock();
for(int i=l;i<=b[bl[l] ].ed;++i){
a[i]+=k;
}
int top1=0,top2=0;
for(int i=b[bl[l] ].st;i<=b[bl[l] ].ed;++i){
if(l<=b[bl[l] ].ov[i-b[bl[l] ].st+1].p){
tmp1[++top1]=b[bl[l] ].ov[i-b[bl[l] ].st+1];
tmp1[top1].x+=k;
}else{
tmp2[++top2]=b[bl[l] ].ov[i-b[bl[l] ].st+1];
}
}
MergeSort(b[bl[l] ].ov,top1,top2);
for(int i=b[bl[r] ].st;i<=r;++i){
a[i]+=k;
}
top1=0,top2=0;
for(int i=b[bl[r] ].st;i<=b[bl[r] ].ed;++i){
if(b[bl[r] ].ov[i-b[bl[r] ].st+1].p<=r){
tmp1[++top1]=b[bl[r] ].ov[i-b[bl[r] ].st+1];
tmp1[top1].x+=k;
}else{
tmp2[++top2]=b[bl[r] ].ov[i-b[bl[r] ].st+1];
}
}
MergeSort(b[bl[r] ].ov,top1,top2);
//tim1+=clock()-t;
}
bool first_time;
Block::node ov1[N];
int len1;
int el2,er2,ela2;
inline int query(int ql,int qr,int k,int uod){//小于等于k的数量
int ans=0;
if(bl[ql]==bl[qr]){
//t=clock();
if(first_time){
len1=0;
for(int i=b[bl[ql] ].st;i<=b[bl[ql] ].ed;++i){
if(
ql<=b[bl[ql] ].ov[i-b[bl[ql] ].st+1].p and
b[bl[ql] ].ov[i-b[bl[ql] ].st+1].p<=qr
){
ov1[++len1]=b[bl[ql] ].ov[i-b[bl[ql] ].st+1];
ov1[len1].x+=b[bl[ql] ].add;
}
}
el2=1;er2=len1;ela2=0;
first_time=0;
}
int l=el2,r=er2,res=0;
if(ov1[len1].x<=k){
ela2=len1;
return len1;
}else if(ov1[1].x<=k){
if(ela2){
if(uod){
el2=l=ela2;
}else{
er2=r=ela2;
}
}
while(l<=r){
int mid=(l+r)>>1;
if(ov1[mid].x<=k){
l=mid+1;res=mid;
}else{
r=mid-1;
}
}
ela2=res;
return res;
}
ela2=0;
//tim3+=clock()-t;
return 0;
}
//t=clock();
if(first_time){
int top1=0,top2=0;
for(int i=b[bl[ql] ].st;i<=b[bl[ql] ].ed;++i){
if(ql<=b[bl[ql] ].ov[i-b[bl[ql] ].st+1].p){
tmp1[++top1]=b[bl[ql] ].ov[i-b[bl[ql] ].st+1];
tmp1[top1].x+=b[bl[ql] ].add;
}
}
for(int i=b[bl[qr] ].st;i<=b[bl[qr] ].ed;++i){
if(b[bl[qr] ].ov[i-b[bl[qr] ].st+1].p<=qr){
tmp2[++top2]=b[bl[qr] ].ov[i-b[bl[qr] ].st+1];
tmp2[top2].x+=b[bl[qr] ].add;
}
}
MergeSort(ov1,top1,top2);
len1=top1+top2;
el2=1;er2=len1;ela2=0;
for(int i=bl[ql]+1;i<=bl[qr]-1;++i){
b[i].el=1;b[i].er=b[i].len();
b[i].ela=0;
}
first_time=0;
}
int l=el2,r=er2,res=0;
if(ov1[len1].x<=k){
ans+=len1;
ela2=len1;
}else if(ov1[1].x<=k){
if(ela2){
if(uod){
el2=l=ela2;
}else{
er2=r=ela2;
}
}
while(l<=r){
int mid=(l+r)>>1;
if(ov1[mid].x<=k){
l=mid+1;res=mid;
}else{
r=mid-1;
}
}
ela2=res;
ans+=res;
}else{
ela2=0;
}
//tim3+=clock()-t;
//t=clock();
for(int i=bl[ql]+1;i<=bl[qr]-1;++i){
l=b[i].el;r=b[i].er;res=0;
if(b[i].ov[b[i].len()].x+b[i].add<=k){
ans+=b[i].len();
b[i].ela=b[i].len();
continue;
}else if(b[i].ov[1].x+b[i].add>k){
b[i].ela=0;
continue;
}
if(uod==1){
b[i].el=l=std::max(l,b[i].ela);
}else if(uod==0){
b[i].er=r=std::min(r,b[i].ela);
}
while(l<=r){
int mid=(l+r)>>1;
if(b[i].ov[mid].x+b[i].add<=k){
l=mid+1;res=mid;
}else{
r=mid-1;
}
}
b[i].ela=res;
ans+=res;
}
//tim4+=clock()-t;
return ans;
}
inline int query_min(int ql,int qr){
if(bl[ql]==bl[qr]){
int res=(int)2e9;
for(int i=ql;i<=qr;++i){
res=std::min(res,a[i]+b[bl[ql] ].add);
}
return res;
}
int res=(int)2e9;
for(int i=ql;i<=b[bl[ql] ].ed;++i){
res=std::min(res,a[i]+b[bl[ql] ].add);
}
for(int i=b[bl[qr] ].st;i<=qr;++i){
res=std::min(res,a[i]+b[bl[qr] ].add);
}
for(int i=bl[ql]+1;i<=bl[qr]-1;++i){
res=std::min(res,b[i].ov[1].x+b[i].add);
}
return res;
}
inline int query_max(int ql,int qr){
if(bl[ql]==bl[qr]){
int res=(int)-2e9;
for(int i=ql;i<=qr;++i){
res=std::max(res,a[i]+b[bl[ql] ].add);
}
return res;
}
int res=(int)-2e9;
for(int i=ql;i<=b[bl[ql] ].ed;++i){
res=std::max(res,a[i]+b[bl[ql] ].add);
}
for(int i=b[bl[qr] ].st;i<=qr;++i){
res=std::max(res,a[i]+b[bl[qr] ].add);
}
for(int i=bl[ql]+1;i<=bl[qr]-1;++i){
res=std::max(res,b[i].ov[b[i].len()].x+b[i].add);
}
return res;
}
inline int kth(int ql,int qr,int k){//第k小
first_time=1;
if(k<=0 or k>qr-ql+1) return -1;
int l=query_min(ql,qr),r=query_max(ql,qr),res=-1;
if(k==1) return l;
if(k==qr-ql+1) return r;
int la=-2e9;
while(l<=r){
int mid=((ll)l+(ll)r)>>1;
if(query(ql,qr,mid,(mid>=la) )<k){
l=mid+1;
}else{
r=mid-1;res=mid;
}
la=mid;
}
return res;
}
int main(){
//file(a);
read(n,m);
//B=std::max(1,(int)sqrt(n)*(int)log2(n) );
//fprintf(stderr,"%d %d\n",B,(n-1)/B);
for(int i=1;i<=n;++i){
read(a[i]);
bl[i]=(i-1)/B;
}
for(int i=0;i<=bl[n];++i){
b[i].st=i*B+1;
b[i].ed=std::min(n,(i+1)*B);
for(int l=b[i].st;l<=b[i].ed;++l){
b[i].ov[l-b[i].st+1]={a[l],l};
}
std::sort(b[i].ov+1,b[i].ov+1+b[i].len(),
[](Block::node x,Block::node y){
return x.x<y.x;
}
);
}
for(int i=1;i<=m;++i){
int op,l,r,k;
read(op,l,r,k);
if(op==1){//query [l,r] k th
printf("%d\n",kth(l,r,k) );
//print(kth(l,r,k) );pc('\n');
}else{//modify [l,r] add k
modify(l,r,k);
}
/*
for(int j=0;j<=bl[n];++j){
fprintf(stderr,"[");
for(int l=b[j].st;l<=b[j].ed;++l){
fprintf(stderr,"%d ",a[l]+b[j].add);
}fprintf(stderr,"] ");
}fprintf(stderr,"\n");
for(int j=0;j<=bl[n];++j){
fprintf(stderr,"(");
for(int l=b[j].st;l<=b[j].ed;++l){
fprintf(stderr,"%d ",b[j].ov[l-b[j].st+1].x+b[j].add);
}fprintf(stderr,") ");
}fprintf(stderr,"\n");
*/
}
/*
fprintf(stderr,"%dms\n",(int)clock() );
fprintf(stderr,"modify full:%dms inco:%dms\n",tim2,tim1);
fprintf(stderr,"query full:%dms inco:%dms\n",tim4,tim3);
*/
return 0;
}
一些比较有意思的轻型分块
区间加, 单点查
分块维护差分。修改 则在 处和 处分别打上 , 标记。查询的时候扫一遍即可。
插入, 第 小
还是按照值域分块。同时维护第 大在哪个块里,对每个块中存在的数维护OV。
每次插入的时候对于所在块重构OV,并修改 个第 大到前一个块中。这个复杂度是 的。
然后查询就是直接查所在块,然后再块内OV找到值即可。(因此还需要维护块间的数的个数前缀和)
一点练习题
就不给题解了,懒了。
动态分块。
静态分块。
[Ynoi2019 模拟赛] Yuno loves sqrt technology I
[Ynoi2019 模拟赛] Yuno loves sqrt technology II
[Ynoi2019 模拟赛] Yuno loves sqrt technology III
莫队
这个比较基础就不再赘述了。
一个对于同时维护多个区间的可以考虑差分。转换为一个多个只关于两个前缀的查询。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具