题目描述
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
输入格式:
第一行两个数n,m。
第二行,n个正整数,为所给定的数列。
输出格式:
n行,第i行的一个数ai,为所求序列中第i个数前m个数的最小值。
输入样例
6 2
7 8 1 43 2
输出样例
0
7
7
1
1
3
数据规模: m≤n≤2000000
解题思路:
这道题目要用RMQ的优化版。纯的RMQ内存空间是要超限的。我们只需要开一维数组即可。通过使用倍增的思想从而达到时间不超限的目的。先输出0,作为第一个答案,再输出a[1]。后面的数据我们先a[1]=min(a[1],a[2]),a[2]=min(a[2],a[3])……,再a[1]=min(a[1],a[1+2^1]), a[2]=min(a[2],a[2+2^1])……,再a[1]=min(a[1],a[1+2^2]), a[2]=min(a[2],a[2+2^2])……这样不断的求最小值,即可求得答案(当然,这是i<m的部分)。最后的n-m的部分,我们只需输出min(a[i],a[i+m-2^trunc(log2(m))])即可。
这道题目用纯的线段树是要时间超时的,能拿到80分,下面我提供一个80分的代码。
这道题目使用zkw线段树是可以通过的,不过比RMQ的优化版要再耗时一些,具体见第三个代码。
代码:(请不要直接拷贝哦)
//RMQ #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> int n,m,k,l,p,a[2000001]; using namespace std; int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]); printf("0\n");//输出第一个答案 if (m>1) printf("%d\n",a[1]); k=log2(m),l=2,p=1; for (int i=1;i<=k;i++) { p*=2;//倍增开始 for (int j=1;j<=n-p+1;j++) a[j]=min(a[j],a[j+p/2]);//不断求区间最小值 while ((l<=p*2)&&(l<m)) { printf("%d\n",min(a[1],a[l-p+1]));//在前m的部分,我们可以这样输出答案 l++; } } for (int i=1;i<=n-m;i++) printf("%d\n",min(a[i],a[i+m-p]));//在m+1到n的部分,我们要另外考虑 return 0; }
//80分 纯线段树 #include <cstdio> using namespace std; struct TREE{ int l,r,min; }tree[8000001]; int n,m,a[4000001]; inline void build(int root,int l,int r) { tree[root].l=l; tree[root].r=r; if (l==r) tree[root].min=a[l]; else { build(root*2,tree[root].l,(l+r)/2); build(root*2+1,(l+r)/2+1,tree[root].r); if (tree[root*2].min<tree[root*2+1].min) tree[root].min=tree[root*2].min; else tree[root].min=tree[root*2+1].min; } } inline int find(int root,int l,int r) { if ((tree[root].l==l)&&(tree[root].r==r)) return tree[root].min; if (r<=(tree[root].l+tree[root].r)/2) return find(root*2,l,r); else if (l>(tree[root].l+tree[root].r)/2) return find(root*2+1,l,r); else { int x=find(root*2,l,(tree[root].l+tree[root].r)/2); int y=find(root*2+1,(tree[root].l+tree[root].r)/2+1,r); return x<y?x:y; } } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); printf("0\n"); for (int i=2;i<=m+1;i++) printf("%d\n",find(1,1,i-1)); for (int i=m+2;i<=n;i++) printf("%d\n",find(1,i-m,i-1)); return 0; }
//100分 zkw线段树 #include<cstdio> #include<algorithm> using namespace std; int n,m,bit,tree[8000005]; int main() { scanf("%d%d",&n,&m); for(bit=1;bit<=n+1;bit<<=1); for(int i=bit+1;i<=bit+n;i++) scanf("%d",&tree[i]); for(int i=bit-1;i;i--) tree[i]=min(tree[i<<1],tree[i<<1|1]);//建树 puts("0"); for(int i=2;i<=n;++i) { int L=max(bit,i-m-1+bit),R=bit+i;//开区间查询 int ans=2e9; for(;L!=(R^1);L>>=1,R>>=1){//直到移到L和R为相邻的兄弟节点 if((L&1)==0) ans=min(ans,tree[L+1]);//如果L为左儿子,那么它的右兄弟肯定在区间内 if((R&1)==1) ans=min(ans,tree[R-1]);//如果R为右儿子,那么它的左兄弟肯定在区间内 } printf("%d\n",ans); } return 0; }