洛谷 P1440 求m区间内的最小值

题目描述

    一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0

输入格式:

    第一行两个数nm

    第二行,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;
}


猜你喜欢

转载自blog.csdn.net/zhouhongkai06/article/details/79844930
今日推荐