-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
11 lines (11 loc) · 24.3 KB
/
Copy pathsearch.xml
File metadata and controls
11 lines (11 loc) · 24.3 KB
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[「分块」数列分块入门1 – 9]]></title>
<url>%2F2019%2F07%2F17%2F%E3%80%8C%E5%88%86%E5%9D%97%E3%80%8D%E6%95%B0%E5%88%97%E5%88%86%E5%9D%97%E5%85%A5%E9%97%A81-%E2%80%93-9%2F</url>
<content type="text"><![CDATA[分块基本思路:不完整的块 的$O(\sqrt{n})$个元素怎么处理?$O(\sqrt{n})$个 整块 怎么处理?要预处理什么信息(复杂度不能超过后面的操作)? 分块入门例题:LOJ#6277. 数列分块入门 1题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间加法,单点查值。 思路修改:大段:标记$add$,$O(1)$维护。局部暴力,$O(\sqrt{n})$修改。 查询:询问时返回元素的值加上其所在块的加法标记$add$ 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include<bits/stdc++.h>using namespace std;const int maxn=5e4+100;int L[maxn],R[maxn],pos[maxn];int add[maxn],a[maxn],b[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return x*f; }void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); memcpy(b,a,sizeof(a)); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo,sort(b+L[i],b+R[i]+1); if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n,sort(b+L[blo],b+R[blo]+1); for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i;}inline void change(int x){ for(int i=L[x];i<=R[x];i++) b[i]=a[i]; sort(b+L[x],b+R[x]+1);}inline void update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(p==q){ for(int i=l;i<=r;i++)a[i]+=c; change(p); } else{ for(int i=p+1;i<=q-1;i++)add[i]+=c; for(int i=l;i<=R[p];i++)a[i]+=c; for(int i=L[q];i<=r;i++)a[i]+=c; change(q),change(p); } }inline int query(int l,int r,int c){ int p=pos[l],q=pos[r]; int ans=0; if(p==q){ for(int i=l;i<=r;i++) if(a[i]+add[p]<c)ans++; } else{ for(int i=p+1;i<=q-1;i++){ ans+=lower_bound(b+L[i],b+R[i]+1,c-add[i])-b-L[i]; } for(int i=l;i<=R[p];i++)if(a[i]+add[p]<c)ans++; for(int i=L[q];i<=r;i++)if(a[i]+add[q]<c)ans++; } return ans;}int main(){ pre(); for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r,c); else printf("%d\n",query(l,r,c*c)); } return 0;} LOJ#6278. 数列分块入门 2 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间加法,询问区间内小于某个值$x^2$的元素个数。 思路沿用上题的add标记,使用$b$数组(或vector)copy $a$数组,作为用于排序(不能改变a数组顺序) 修改:大段:标记$add$,$O(1)$维护。局部:暴力,$(\sqrt{n})$修改,并修改对应$b$数组局部值。 查询:大段:每次lower_bound,$O(log\sqrt{n})$二分查找$x^2$减去加法标记$add$。局部:暴力$O(\sqrt{n})$查找 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include<bits/stdc++.h>using namespace std;const int maxn=5e4+100;int L[maxn],R[maxn],pos[maxn];int add[maxn],a[maxn],b[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return x*f; }void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); memcpy(b,a,sizeof(a)); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n,sort(b+L[blo],b+R[blo]+1); for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i;}void change(int x){ for(int i=L[x];i<=R[x];i++) b[i]=a[i];}void update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(p==q){ for(int i=l;i<=r;i++)a[i]+=c; change(p); } else{ for(int i=p+1;i<=q-1;i++)add[i]+=c; for(int i=l;i<=R[p];i++)a[i]+=c; for(int i=L[q];i<=r;i++)a[i]+=c; change(q),change(p); } }int query(int l,int r,int c){ int p=pos[l],q=pos[r]; int ans=0; if(p==q){ for(int i=l;i<=r;i++) if(a[i]+add[p]<c)ans++; } else{ for(int i=p+1;i<=q-1;i++){ sort(b+L[i],b+R[i]+1); ans+=lower_bound(b+L[i],b+R[i]+1,c-add[i])-b-L[i]; } for(int i=l;i<=R[p];i++)if(a[i]+add[p]<c)ans++; for(int i=L[q];i<=r;i++)if(a[i]+add[q]<c)ans++; } return ans;}int main(){ pre(); for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r,c); else printf("%d\n",query(l,r,c*c)); } return 0;} LOJ#6278. 数列分块入门 3 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间加法,询问区间内小于某个值$x$的前驱(比其小的最大元素)。 思路换汤不换药,任同上题二分。或者在块内维护一个set等价。通常在块内维护另一个数据结构会使分块更灵活。 修改:大段:标记$add$,$O(1)$维护。局部:暴力,$O(\sqrt{n})$修改,并修改对应$b$数组局部值。 查询:大段:每次lower_bound,$O(log\sqrt{n})$二分查找$x^2$(减去加法表记$add$)。同时注意边界情况。局部:暴力$O(\sqrt{n})$查找(注意加上加法表记$add$) 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include<bits/stdc++.h>#define inf 0x3f3f3f3fusing namespace std;const int maxn=1e5+5;int L[maxn],R[maxn],pos[maxn];int a[maxn],add[maxn],b[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;}void upd(int x){ for(int i=L[x];i<=R[x];i++) b[i]=a[i]; sort(b+L[x],b+R[x]+1);}void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=b[i]=read(); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo,sort(b+L[i],b+R[i]+1); if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n,sort(b+L[blo],b+R[blo]+1); for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i;}void update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(q==p) for(int i=l;i<=r;i++)a[i]+=c,upd(q); else{ for(int i=p+1;i<=q-1;i++)add[i]+=c; for(int i=l;i<=R[p];i++)a[i]+=c; for(int i=L[q];i<=r;i++)a[i]+=c; upd(q),upd(p); }}int query(int l,int r,int c){ int p=pos[l],q=pos[r]; int ans=-inf; if(q==p){ for(int i=l;i<=r;i++) if(a[i]+add[p]<c)ans=max(ans,a[i]+add[p]); } else{ for(int i=p+1;i<=q-1;i++){ if(b[R[i]]+add[i]<c)ans=max(ans,b[R[i]]+add[i]); if(b[L[i]]+add[i]>=c)continue; else ans=max(ans,b[lower_bound(b+L[i],b+R[i]+1,c-add[i])-b-1]+add[i]); } for(int i=l;i<=R[p];i++)if(a[i]+add[p]<c)ans=max(ans,a[i]+add[p]); for(int i=L[q];i<=r;i++)if(a[i]+add[q]<c)ans=max(ans,a[i]+add[q]); } return ans==-inf?-1:ans;}void work(){ while(n--){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r,c); else printf("%d\n",query(l,r,c)); }}int main(){ pre(); work(); return 0;} LOJ#6278. 数列分块入门 4 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间加法,区间求和。 思路需要多护每个块的元素和,先要预处理一个期间和$sum$。 修改:大段:标记$add$,$O(1)$维护。局部:暴力,$O(\sqrt{n})$单点修改,同时sum加上局部和。 查询:大段:$O(1)$询问区间和$sum$加上其所在块的加法标记$add$区间长局部:暴力$O(\sqrt{n})$查询单点(注意加上加法标记$add$区间长) 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566#include<bits/stdc++.h>using namespace std;const int maxn=5e4+10;int L[maxn],R[maxn],pos[maxn];long long add[maxn],a[maxn],sum[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;}void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo]+1,R[blo]=n; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) sum[i]+=a[j],pos[j]=i;}void update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(p==q){ for(int i=l;i<=r;i++)a[i]+=c; sum[p]+=c*(r-l+1); } else{ for(int i=p+1;i<=q-1;i++)add[i]+=c; for(int i=l;i<=R[p];i++)a[i]+=c; sum[p]+=c*(r-l+1); for(int i=L[q];i<=r;i++)a[i]+=c; sum[p]+=c*(r-l+1); }}long long query(int l,int r,int c){ int p=pos[l],q=pos[r]; long long res=0; if(p==q){ for(int i=l;i<=r;i++)res+=a[i]; res+=add[p]*(r-l+1); } else{ for(int i=p+1;i<=q-1;i++)res+=sum[i]+add[i]*(R[i]-L[i]+1); for(int i=l;i<=R[p];i++)res+=a[i]; res+=add[p]*(R[p]-l+1); for(int i=L[q];i<=r;i++)res+=a[i]; res+=add[q]*(r-L[q]+1); } return res%c;}void work(){ for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r,c); else printf("%d\n",query(l,r,c+1)); }}int main(){ pre(); work(); return 0;} LOJ#6278. 数列分块入门 5 题目描述给出一个长为$n$的数列 ,以及$n$个操作,操作涉及区间开方,区间求和。 思路本题不同于其他题,看似并没有很好的维护方法,必须另辟蹊径。注意到向下取整开方,而一个数经过几次开方之后,它的值就会变成0或者1。而0或者1开方值不变使用$tag$标记该区间是否全为0或1 修改:大段:询问标记记$tag?ture\to$跳过:$false\toO(\sqrt{n})$单点修改,同时维护区间和$sum$,01标记$tag$ 局部:询问标记记$tag?ture\to$跳过:$false\toO(\sqrt{n})$单点修改,维护区间和$sum$。 查询:大段:$O(1)$询问区间和$sum$局部:暴力$O(\sqrt{n})$查询单点 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include<bits/stdc++.h>using namespace std;const int maxn=5e5+10;int L[maxn],R[maxn],pos[maxn];int a[maxn],tag[maxn],sum[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;}void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) sum[i]+=a[j],pos[j]=i;}void upd(int x,int l=0,int r=0){ bool mark=1,flag=1; if(!l&&!r)l=L[x],r=R[x],mark=0; for(int i=l;i<=r;i++){ sum[x]-=a[i];a[i]=sqrt(a[i]);sum[x]+=a[i]; if(a[i]!=0&&a[i]!=1)flag=0; } if(!mark)tag[x]=flag;}void update(int l,int r){ int p=pos[l],q=pos[r]; if(p==q){ if(!tag[p]){ upd(p,l,r); } } else{ for(int i=p+1;i<=q-1;i++){ if(!tag[i]){ upd(i,0,0); } } if(!tag[p])upd(p,l,R[p]); if(!tag[q])upd(q,L[q],r); }}int query(int l,int r){ int p=pos[l],q=pos[r]; int res=0; if(p==q){ for(int i=l;i<=r;i++) res+=a[i]; } else{ for(int i=p+1;i<=q-1;i++)res+=sum[i]; for(int i=l;i<=R[p];i++)res+=a[i]; for(int i=L[q];i<=r;i++)res+=a[i]; } return res;}void work(){ for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r); else printf("%d\n",query(l,r)); }}int main(){ pre(); work(); return 0;} LOJ#6278. 数列分块入门 6 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及单点插入,单点询问,数据随机生成。 思路用$vector$维护每个块,插入会很方便\随机数据只需要$vector$乱搞一波,插入均摊依然是$O(\sqrt {n})$,但如果数据不随机,那么一个块也许会大大超过$\sqrt {n}$。所以我们需要重建操作,每次重建$O(n)$,重建最多$\sqrt {n}$次 修改:先查询插入所在区间,再暴力插入 查询:每次减去大段$vector$维护块中的数量,找到插入区间。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859#include<bits/stdc++.h>using namespace std;const int maxn=100000+100,md=1000+10;int a[maxn<<1];vector<int>v[md];int n,m,blo;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar(); } return x*f;}void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); blo=sqrt(n); for(int i=1;i<=n;i++) v[(i-1)/blo+1].push_back(a[i]); m=(n-1)/blo+1;}void rebuild(){ int tot=0; for(int i=1;i<=m;i++){ for(vector<int>::iterator j=v[i].begin();j!=v[i].end();j++) a[++tot]=*j; v[i].clear(); } blo=sqrt(tot); for(int i=1;i<=tot;i++) v[(i-1)/blo+1].push_back(a[i]); m=(tot-1)/blo+1;}pair<int,int> query(int rk){ int x=1; while(rk>v[x].size()) rk-=v[x].size(),x++; return make_pair(x,rk-1); }void update(int l,int r){ pair<int,int>t=query(l); v[t.first].insert(v[t.first].begin()+t.second,r); if(v[t.first].size()>20*blo)rebuild(); }void work(){ for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)update(l,r); else { pair<int,int>t=query(r); printf("%d\n",v[t.first][t.second]); } }}int main(){ pre(); work(); return 0;} LOJ#6278. 数列分块入门 7 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间乘法,区间加法,单点询问。 思路维护思路大概同数列分块入门 1相同,而不同是需要同时维护两种标记乘法标记$mul$和加法标记$add$。重点在于两种标记的维护顺序,乘法标记优先级高于加法标记 修改:大段:区间加法只需维护$add+c$,乘法标记需要同时维护$add$与$mul$。先$addc$,再$mulc$局部:需要先下放区间标记,再暴力暴力$O(\sqrt{n})$修改 查询:询问时返回元素的值先乘所在块的乘法标记$mul$再加上所在块的加法标记$add$ 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778#include<bits/stdc++.h>#define mod 10007using namespace std;const int maxn=1e5+100;int a[maxn],sum[maxn];int L[maxn],R[maxn],pos[maxn];int add[maxn],mul[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;}void pre(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(),a[i]%=mod; int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo,mul[i]=1,add[i]=0; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n,mul[blo]=1,add[blo]=0; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i;}void pushdown(int x){ mul[x]%=mod,add[x]%=mod; for(int i=L[x];i<=R[x];i++) a[i]*=mul[x],a[i]+=add[x],a[i]%=mod; add[x]=0,mul[x]=1;}void add_update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(p==q){ pushdown(p); for(int i=l;i<=r;i++)a[i]+=c,a[i]%=mod; } else{ for(int i=p+1;i<=q-1;i++)add[i]+=c,add[i]%=mod; pushdown(p); for(int i=l;i<=R[p];i++)a[i]+=c,a[i]%=mod; pushdown(q); for(int i=L[q];i<=r;i++)a[i]+=c,a[i]%=mod; }}void mul_update(int l,int r,int c){ int p=pos[l],q=pos[r]; if(p==q){ pushdown(p); for(int i=l;i<=r;i++)a[i]*=c,a[i]%=mod; } else{ for(int i=p+1;i<=q-1;i++)add[i]*=c,mul[i]*=c,add[i]%=mod,mul[i]%=mod; pushdown(p); for(int i=l;i<=R[p];i++)a[i]*=c,a[i]%=mod; pushdown(q); for(int i=L[q];i<=r;i++)a[i]*=c,a[i]%=mod; }}int query(int r){ int q=pos[r]; return (a[r]*mul[q]+add[q])%mod;}void work(){ for(int i=1;i<=n;i++){ int op=read(),l=read(),r=read(),c=read(); if(!op)add_update(l,r,c); else if(op==1)mul_update(l,r,c%mod); else printf("%d\n",query(r)); } }int main(){ pre(); work(); return 0; } LOJ#6278. 数列分块入门 8 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及区间询问等于一个数$c$的元素,并将这个区间的所有元素改为$c$。 思路同数列分块入门 5,这题看似也并没有什么什么好的维护方法,但其实不难想到我们依然可以沿用$vis$标记区间是否为相同的数,从而实现区间$O(1)$的查询,再此之前只需要暴力修改。很容易想到最多$\sqrt {n}$我们可以使序列变为相同的数。复杂度依然为$O(n\sqrt {n})$ 查询修改:大段:询问标记记$vis?ture\toO(1)$的查询,并维护异同标记$tag$:$false\toO(\sqrt{n})$单点修改,同时维护异同标记$tag$局部:询问标记记$vis?ture\toO(1)$的查询,并修改异同标记$tag$为0:$false\toO(\sqrt{n})$单点修改,修改异同标记$tag$为0。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475#include<bits/stdc++.h>#define inf 0x3f3f3f3fusing namespace std;const int maxn=1e5+100;int L[maxn],R[maxn],pos[maxn];int a[maxn],vis[maxn];int n;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;}void pre(){ n=read(); for(int i=1;i<=n;i++)a[i]=read(); int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i;}void upd(int x){ if(!vis[x])return; for(int i=L[x];i<=R[x];i++) a[i]=vis[x]; vis[x]=0;}int query(int l,int r,int c){ int p=pos[l],q=pos[r]; int ans=0; if(p==q){ upd(p); for(int i=l;i<=r;i++){ if(a[i]==c)ans++; a[i]=c; } } else{ for(int i=p+1;i<=q-1;i++){ if(!vis[i]){ for(int j=L[i];j<=R[i];j++){ if(a[j]==c)ans++; a[j]=c; } } else if(vis[i]==c)ans+=R[i]-L[i]+1; vis[i]=c; } upd(p); for(int i=l;i<=R[p];i++){ if(a[i]==c)ans++; a[i]=c; } upd(q); for(int i=L[q];i<=r;i++){ if(a[i]==c)ans++; a[i]=c; } } return ans;}void work(){ for(int i=1;i<=n;i++){ int l=read(),r=read(),c=read(); printf("%d\n",query(l,r,c)); }}int main(){ pre(); work(); return 0;} LOJ#6278. 数列分块入门 9 题目描述给出一个长为$n$的数列,以及$n$个操作,操作涉及询问区间的最小众数。 思路分块经典难题了,其实不难维护,重点在于预处理 大段:预处理整块之间的众数$f[i][j]$来表示,$O(n\sqrt{n})$ 预处理。 小段:二分暴力查询 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#include<bits/stdc++.h>using namespace std;const int maxn=100000+100,md=1000+100;int f[md][md];int L[md],R[md],pos[maxn];int a[maxn],cnt[maxn];int val[maxn];map<int,int>ma;vector<int>v[maxn];int n,m,id=0,x=0;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;} void cal(int p){ memset(cnt,0,sizeof(cnt)); int ans=0; for(int i=L[p];i<=n;i++){ cnt[a[i]]++; int q=pos[i]; if(cnt[a[i]]>cnt[ans]||(cnt[a[i]]==cnt[ans]&&val[a[i]]<val[ans])) ans=a[i]; f[p][q]=ans; }} void pre(){ n=read(); for(int i=1;i<=n;i++){ a[i]=read(); if(!ma[a[i]]){ ma[a[i]]=++id; val[id]=a[i]; } a[i]=ma[a[i]]; v[a[i]].push_back(i); } int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i; for(int i=1;i<=blo;i++)cal(i);}int query(int x,int l,int r){ return upper_bound(v[x].begin(),v[x].end(),r)-lower_bound(v[x].begin(),v[x].end(),l);}int query(int l,int r){ int p=pos[l],q=pos[r]; int ans=0,mt=0; if(p==q){ for(int i=l;i<=r;i++){ int t=query(a[i],l,r); if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } } else{ ans=f[p+1][q-1]; mt=v[ans].size(); for(int i=l;i<=R[p];i++){ int t=query(a[i],l,r); if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } for(int i=L[q];i<=r;i++){ int t=query(a[i],l,r); if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } } return val[ans];}void work(){ for(int i=1;i<=n;i++){ int l=read() ,r=read(); x=query(l,r); printf("%d\n",x); }}int main(){ pre(); work(); return 0;} 但这样的复杂度是$O(n\sqrt{n} logn)$的,所以我们考虑如何去掉这个$logn$ 于是考虑两个数组: $c[i][x]$表示前$i$块(包含$i$)$x$的总数 $s[i][j][x]$表示第$i$块前$j$个数(包含$j$)$x$的总数 这两个数组都是可以在$O(n\sqrt[2]{n})$内处理的。至于查询操作不再赘述。类似于前缀和的$O(1)$ 当然还少不了离散化 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697#include<bits/stdc++.h>using namespace std;const int maxn=100000+5,md=320+5;int f[md][md];int c[md][maxn];int s[md][md][md];int L[md],R[md],pos[maxn];int a[maxn],cnt[maxn];int val[maxn];int vis[md][maxn];map<int,int>mp;int n,m,id=0,x=0;inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} return x*f;} void cal(int p){ memset(cnt,0,sizeof(cnt)); int ans=0; for(int i=L[p];i<=n;i++){ cnt[a[i]]++; int q=pos[i]; if(cnt[a[i]]>cnt[ans]||(cnt[a[i]]==cnt[ans]&&val[a[i]]<val[ans])) ans=a[i]; f[p][q]=ans; } memset(cnt,0,sizeof(cnt)); for(int i=1;i<=id;i++) c[p][i]=c[p-1][i]; for(int i=L[p];i<=R[p];i++) c[p][a[i]]++;//预处理c int tot=0; for(int j=1,i=L[p];i<=R[p];i++,j++){ if(!vis[p][a[i]])vis[p][a[i]]=++tot; cnt[vis[p][a[i]]]++; for(int k=1;k<=tot;k++) s[p][j][k]=cnt[k]; }//二次离散 } void pre(){ n=read(); for(int i=1;i<=n;i++){ a[i]=read(); if(!mp[a[i]]){ mp[a[i]]=++id; val[id]=a[i]; } a[i]=mp[a[i]]; } int blo=sqrt(n); for(int i=1;i<=blo;i++) L[i]=(i-1)*blo+1,R[i]=i*blo; if(R[blo]<n)blo++,L[blo]=R[blo-1]+1,R[blo]=n; for(int i=1;i<=blo;i++) for(int j=L[i];j<=R[i];j++) pos[j]=i; for(int i=1;i<=blo;i++)cal(i);}int query(int l,int r){ int p=pos[l],q=pos[r]; int ans=0,mt=0; if(p==q){ for(int i=l;i<=r;i++){ int t=s[p][r-L[p]+1][vis[p][a[i]]]-s[p][l-L[p]][vis[p][a[i]]]; if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } } else{ ans=f[p+1][q-1]; mt=c[q-1][ans]-c[p][ans]; for(int i=l;i<=R[p];i++){ int t=c[q-1][a[i]]-c[p-1][a[i]]-s[p][l-L[p]][vis[p][a[i]]]+s[q][r-L[q]+1][vis[q][a[i]]]; if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } for(int i=L[q];i<=r;i++){ int t=c[q-1][a[i]]-c[p-1][a[i]]-s[p][l-L[p]][vis[p][a[i]]]+s[q][r-L[q]+1][vis[q][a[i]]];; if(t>mt||(t==mt&&val[a[i]]<val[ans])) ans=a[i],mt=t; } } return val[ans];}void work(){ for(int i=1;i<=n;i++){ int l=read(),r=read(); printf("%d\n",query(l,r)); }}int main(){ pre(); work(); return 0;}]]></content>
<tags>
<tag>数据结构--分块</tag>
</tags>
</entry>
</search>