区间 kth
众所周知,区间 kth 有很多种求法。
本文中的时间复杂度和分数均以实现 P3834 为准。
为了更好地贴合现实,本文代码将更加符合学此算法时的实际情况。
排序
通过选择 / 冒泡 / 插入排序,将区间排序后输出 k 小值。
时间复杂度 。
代码长度:425B | 用时:7.81s | 内存:1.71MB
#include<cstdio> using namespace std; int n,m,l,r,k; int a[200007],s[200007]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&k); for(int i=l;i<=r;i++) s[i-l+1]=a[i]; for(int i=r-l+1;i>0;i--) for(int j=1;j<i;j++) if(s[j]>s[j+1]) {int t=s[j];s[j]=s[j+1];s[j+1]=t;} printf("%d\n",s[k]); } return 0; }
sort
作为 C 党,有必要了解 STL。
通过 sort 将排序的时间复杂度降下来(当然也可以自己写)
时间复杂度 。
代码长度:353B | 用时:6.49s | 内存:1.83MB
#include<cstdio> #include<algorithm> using namespace std; int n,m,l,r,k; int a[200007],s[200007]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&k); for(int i=l;i<=r;i++) s[i-l+1]=a[i]; sort(s+1,s+r-l+2); printf("%d\n",s[k]); } return 0; }
分治
sort 的思想是二分,但我们要找的 kth 只可能出现在二分中的其中一个部分,那么就没有必要对两个部分都递归,而可以只对其中一部分递归。
时间复杂度 。
代码长度:579B | 用时:6.44s | 内存:1.84MB
#include<cstdio> #include<algorithm> using namespace std; int n,m,l,r,k; int a[200007],s[200007]; void _sort(int l,int r,int k) { int i=l,j=r,v=s[l]; while(i<=j) { while(s[i]<v)i++; while(s[j]>v)j--; if(i<=j)swap(s[i],s[j]),i++,j--; } if(k<=j)_sort(l,j,k); else if(i<=k)_sort(i,r,k); else return; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&k); for(int i=l;i<=r;i++) s[i-l+1]=a[i]; _sort(1,r-l+1,k); printf("%d\n",s[k]); } return 0; }
nth_element
为了避免重复造轮子,STL 中已经收录了 求 k 小值的函数 nth_element
(虽然还是重复造轮子了)。
时间复杂度 。
代码长度:364B | 用时:6.43s | 内存:1.85MB
#include<cstdio> #include<algorithm> using namespace std; int n,m,l,r,k; int a[200007],s[200007]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&k); for(int i=l;i<=r;i++) s[i-l+1]=a[i]; nth_element(s+1,s+k,s+r-l+2); printf("%d\n",s[k]); } return 0; }
值域映射
一种思想叫做从值域考虑,把区间里的数在值域上进行一个映射,然后倒着扫,扫到一个数就 , 的时候输出。
时间复杂度 。
代码长度:883B | 用时:6.42s | 内存:1.95MB
#include<cstdio> #include<algorithm> using namespace std; int re() { int s=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=s*10+ch-48,ch=getchar(); return s*f; } void wr(int s) { if(s<0)putchar('-'),s=-s; if(s>9)wr(s/10); putchar(s%10+48); } const int inf=2e5+7; int n,m; int a[inf],bok[inf],T[inf]; int main() { n=re();m=re(); for(int i=1;i<=n;i++) bok[i]=a[i]=re(); sort(bok+1,bok+n+1); int num=unique(bok+1,bok+n+1)-bok-1; for(int i=1;i<=n;i++) a[i]=lower_bound(bok+1,bok+num+1,a[i])-bok; for(int i=1;i<=m;i++) { int l=re(),r=re(),k=re(); for(int j=l;j<=r;j++) T[a[j]]++; for(int j=1;j<=num;j++) { k-=T[j]; if(k<=0) { wr(bok[j]),putchar('\n'); break; } } for(int j=l;j<=r;j++) T[a[j]]--; } return 0; }
归并树
用线段树套 vector,在 的时间里建立归并树。
查询时二分答案,每次二分一个 mid,求区间内有多少数小于 mid,即在线段树的对应节点里的 vector 中再二分。
时间复杂度 。
代码长度:1.94KB | 用时:5.84s | 内存:53.14MB
#include<cstdio> #include<algorithm> #include<vector> using namespace std; inline int re() { int s=0;char ch=getchar(); while(ch>'9'||ch<'0')ch=getchar(); while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48),ch=getchar(); return s; } inline void wr(int s) { if(s>9)wr(s/10); putchar(s%10+48); } const int inf=2e5+7; int n,m,num,a[inf],bok[inf]; struct Seg_Tree{ int le,ri; vector<int>h; }T[inf<<2]; inline void build(int i,int l,int r) { T[i].le=l,T[i].ri=r; if(l==r) { T[i].h.push_back(a[l]); return; } int mid=(l+r)>>1; build(i<<1,l,mid); build(i<<1|1,mid+1,r); vector<int>::iterator lc=T[i<<1].h.begin(),rc=T[i<<1|1].h.begin(); while(lc!=T[i<<1].h.end()&&rc!=T[i<<1|1].h.end()) { if(*lc<*rc)T[i].h.push_back(*lc),lc++; else T[i].h.push_back(*rc),rc++; } while(lc!=T[i<<1].h.end()) T[i].h.push_back(*lc),lc++; while(rc!=T[i<<1|1].h.end()) T[i].h.push_back(*rc),rc++; } inline int judge(int i,int l,int r,int k) { if(l<=T[i].le&&T[i].ri<=r) return lower_bound(T[i].h.begin(),T[i].h.end(),k)-T[i].h.begin(); int mid=(T[i].le+T[i].ri)>>1,ans=0; if(l<=mid)ans+=judge(i<<1,l,r,k); if(mid<r)ans+=judge(i<<1|1,l,r,k); return ans; } inline int ask(int x,int y,int k) { int l=1,r=num; while(l<r) { int mid=(l+r+1)>>1; int ls=judge(1,x,y,mid); if(ls<k)l=mid; else if(ls>=k)r=mid-1; } return bok[l]; } int main() { n=re();m=re(); for(register int i=1;i<=n;i++) bok[i]=a[i]=re(); sort(bok+1,bok+n+1); num=unique(bok+1,bok+n+1)-bok-1; for(register int i=1;i<=n;i++) a[i]=lower_bound(bok+1,bok+num+1,a[i])-bok; build(1,1,n); for(register int i=1;i<=m;i++) { int l=re(),r=re(),k=re(); wr(ask(l,r,k)),putchar('\n'); } return 0; }
主席树
发明者的原话是:“对于原序列的每一个前缀 建出一棵线段树维护值域上每个数出现的次数,则其树是可减的。”
其实就是前缀和。
权值线段树储存的是值域上数的个数。对于版本 R 的权值线段树减去版本 L-1 的权值线段树,就得到了维护 [L,R] 的权值线段树。
时间复杂度 。
代码长度:1.33KB | 用时:537ms | 内存:39.96MB
#include<cstdio> #include<algorithm> using namespace std; int re() { int s=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=s*10+ch-48,ch=getchar(); return s*f; } void wr(int s) { if(s<0)putchar('-'),s=-s; if(s>9)wr(s/10); putchar(s%10+48); } const int inf=2e5+7; int n,m,num,a[inf],bok[inf]; struct Seg_Tree{ int lson,rson; int sum; #define lc(i) T[i].lson #define rc(i) T[i].rson }T[inf<<5]; int rot[inf],cnt; void insert(int j,int &i,int l,int r,int k) { i=++cnt;T[i]=T[j]; if(l==r){T[i].sum++;return;} int mid=(l+r)>>1; if(k<=mid)insert(lc(j),lc(i),l,mid,k); else insert(rc(j),rc(i),mid+1,r,k); T[i].sum=T[lc(i)].sum+T[rc(i)].sum; } int ask(int i,int j,int l,int r,int k) { if(l==r)return l; int mid=(l+r)>>1,sum=T[lc(j)].sum-T[lc(i)].sum; if(k<=sum)return ask(lc(i),lc(j),l,mid,k); return ask(rc(i),rc(j),mid+1,r,k-sum); } int main() { n=re();m=re(); for(int i=1;i<=n;i++) bok[i]=a[i]=re(); sort(bok+1,bok+n+1); num=unique(bok+1,bok+n+1)-bok-1; for(int i=1;i<=n;i++) a[i]=lower_bound(bok+1,bok+num+1,a[i])-bok; for(int i=1;i<=n;i++) insert(rot[i-1],rot[i],1,num,a[i]); for(int i=1;i<=m;i++) { int l=re(),r=re(),k=re(); wr(bok[ask(rot[l-1],rot[r],1,num,k)]),putchar('\n'); } return 0; }
划分树
维护一个节点里面有多少数被划分到左边,有多少数被划分到右边,然后在线段树上查询。
由于稳定排序,在父节点的相对位置与在子节点中的相对位置相同。
时间复杂度 。
代码长度:1.36KB | 用时:419ms | 内存:29.69MB
#include<cstdio> #include<algorithm> using namespace std; int re() { int s=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=s*10+ch-48,ch=getchar(); return s*f; } void wr(int s) { if(s<0)putchar('-'),s=-s; if(s>9)wr(s/10); putchar(s%10+48); } const int inf=2e5+7; int n,m,bok[inf]; struct Seg_Tree{ int a[inf],sum[inf]; }T[20]; void build(int dep,int l,int r) { if(l==r)return; int mid=(l+r)>>1,k=bok[mid]; int sum=0,cnt=mid-l+1; for(int i=l;i<=r;i++) if(T[dep].a[i]<k)cnt--; int il=l-1,ir=mid; for(int i=l;i<=r;i++) { int now=T[dep].a[i]; if(now<k)sum++,T[dep+1].a[++il]=now; else if(now==k&&cnt) { sum++,cnt--; T[dep+1].a[++il]=now; } else T[dep+1].a[++ir]=now; T[dep].sum[i]=sum; } build(dep+1,l,mid); build(dep+1,mid+1,r); } int ask(int dep,int l,int r,int x,int y,int k) { if(l==r)return T[dep].a[l]; int mid=(l+r)>>1; int s1=0,s2=T[dep].sum[y]; if(x^l)s1=T[dep].sum[x-1]; if(k<=s2-s1)return ask(dep+1,l,mid,l+s1,l+s2-1,k); return ask(dep+1,mid+1,r,x-s1+mid-l+1,y-s2+mid-l+1,k-s2+s1); } int main() { n=re();m=re(); for(int i=1;i<=n;i++) T[1].a[i]=bok[i]=re(); sort(bok+1,bok+n+1); build(1,1,n); for(int i=1;i<=m;i++) { int l=re(),r=re(),k=re(); wr(ask(1,1,n,l,r,k)),putchar('\n'); } return 0; }
树套树
外层维护权值,内层维护下标。
每次查询在左儿子中下标在 中的数量,然后判断查询左子树还是查询右子树。
时空复杂度 ,但支持修改。
代码长度:1.66KB | 用时:4.23s | 内存:256.00MB
#include<cstdio> #include<algorithm> using namespace std; int re() { int s=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=s*10+ch-48,ch=getchar(); return s*f; } void wr(int s) { if(s<0)putchar('-'),s=-s; if(s>9)wr(s/10); putchar(s%10+48); } const int inf=2e5+7; int n,m,a[inf]; int bok[inf],num; struct Seg_Tree{ int lc,rc,val; }T[inf*300]; int cnt; void change(int &i,int l,int r,int pos,int k) { if(i==0)i=++cnt; if(l==r) { T[i].val+=k; return; } int mid=(l+r)>>1; if(pos<=mid)change(T[i].lc,l,mid,pos,k); else change(T[i].rc,mid+1,r,pos,k); T[i].val=T[T[i].lc].val+T[T[i].rc].val; } int query(int i,int l,int r,int x,int y) { if(x<=l&&r<=y)return T[i].val; int mid=(l+r)>>1,ans=0; if(x<=mid)ans+=query(T[i].lc,l,mid,x,y); if(mid<y)ans+=query(T[i].rc,mid+1,r,x,y); return ans; } int rot[inf<<2]; void insert(int i,int l,int r,int k,int v) { change(rot[i],1,n,v,1); if(l==r)return; int mid=(l+r)>>1; if(k<=mid)insert(i<<1,l,mid,k,v); else insert(i<<1|1,mid+1,r,k,v); } int ask(int i,int l,int r,int x,int y,int k) { if(l==r)return bok[l]; int mid=(l+r)>>1,sum=query(rot[i<<1],1,n,x,y); if(k<=sum)return ask(i<<1,l,mid,x,y,k); else return ask(i<<1|1,mid+1,r,x,y,k-sum); } int main() { n=re();m=re(); for(int i=1;i<=n;i++) bok[i]=a[i]=re(); sort(bok+1,bok+n+1); num=unique(bok+1,bok+n+1)-bok-1; for(int i=1;i<=n;i++) { a[i]=lower_bound(bok+1,bok+num+1,a[i])-bok; insert(1,1,num,a[i],i); } for(int i=1;i<=m;i++) { int l=re(),r=re(),k=re(); wr(ask(1,1,num,l,r,k)),putchar('\n'); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具