zoukankan      html  css  js  c++  java
  • 【题解】区间中最大的数

    //本文首发于简书:https://www.jianshu.com/p/d5847d795eec
    题目链接:https://vjudge.net/contest/389106#problem/D

    D - 区间中最大的数(课堂练习)

    给出一个有N个数的序列,编号0 - N - 1。进行Q次查询,查询编号i至j的所有数中,最大的数是多少。
    例如: 1 7 6 3 1。i = 1, j = 3,对应的数为7 6 3,最大的数为7。(该问题也被称为RMQ问题)
    Input
    第1行:1个数N,表示序列的长度。(2 <= N <= 10000) 第2 - N + 1行:每行1个数,对应序列中的元素。(0 <= Si <= 10^9) 第N + 2行:1个数Q,表示查询的数量。(2 <= Q <= 10000) 第N + 3 - N + Q + 2行:每行2个数,对应查询的起始编号i和结束编号j。(0 <= i <= j <= N - 1)
    Output
    共Q行,对应每一个查询区间的最大值。
    Sample Input
    5
    1
    7
    6
    3
    1
    3
    0 1
    1 3
    3 4
    Sample Output
    7
    7
    3

    题目解析&错误示范

    这道题很显然,是一道rmq问题,因为题目已经声明了……
    这就是一个很模板类的题目,但是小编仍然错了好多次,那么就讲一讲rmq具体怎么理解吧。
    错了8次
    对于rmq问题来说一般对于空间和时间都有一定要求,空间上开不起10000*10000的二维数组,时间上也总会超时。
    image.png

    #include<cstdio>
    using namespace std;
    int n,num[10000],q,x,y;
    int head=0,tail=0;
    int rmq[5000][5000];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=0;i<n;i++)
    	scanf("%d",&num[i]);
    	for(int i=0;i<n;i++)
    	for(int j=i;j<n;j++)
    	{
    		if(i==j) rmq[i][j]=num[i];
    		else rmq[i][j]=num[j]>rmq[i][j-1]? num[j]:rmq[i][j-1];
    	}
    	scanf("%d",&q);
    	for(int i=1;i<=q;i++)
    	{
    		scanf("%d%d",&x,&y);
    		printf("%d 
    ",rmq[x][y]);
    	}
    	return 0;
    }
    

    这是小编最开始暴力求解的代码,时间复杂度为O(n^2)。
    为了节约时间不得不采用O(n log n)的做法,那就是采用分治的思想。
    设数组rmq[i][j]表示从i开始(包括i)后面2^j个数中的最大值,这样递推的关系式就变成了:

    • 当j!=0时,rmq[i][j]=max(rmq[i][j-1],rmq[i+2^(j-1)][j-1]) (分成区间[i,2(j-1)]和[2(j-1)+1,2^j])
    • 当j==0时,rmq[i][j]=num[i] (因为这时区间内只有1个数)
      查询就困难了,关系式:
      max(rmq[l][k],rmq[r-(1<<k)+1][k])
      但是查询更难理解,不过画个图就ok了:
      忽略我拙劣的画技
      为什么这个关系式设置成这样呢?假如区间长度为8(或其他2的次方数),那么就会恰好发现这样两区间就是[1,4]和[5,8],2的k次恰好就是区间长度的一半,再加一就是下一段的起始位置,加入这个区间长度不是2的整数次,那么只能多,不能少,即使是如图的长度为6,那么也当作8来算,只不过有重叠,但是查询依然是O(1)。
      错误示范:
    #include<bits/stdc++.h>
    using namespace std;
    unsigned long long n,num[10000],q,l,r;
    unsigned logg[10000],rmq[10000][50];
    int main()
    {
    	cin>>n;
    	logg[1]=0;
    	for(int i=2;i<=10000;i++)
    	logg[i]=logg[i/2]+1;
    	for(int i=0;i<n;i++)
    	cin>>num[i];
    	for(int i=0;i<n;i++)
    	for(int j=0;j<=logg[n];j++)
    	{
    		if(j==0) rmq[i][j]=max(num[i],num[i+1]);
    		else rmq[i][j]=max(rmq[i][j-1],rmq[i+(unsigned long long)(1<<(j-1)) ][j-1]);
    	}
    	cin>>q;
    	for(int i=1;i<=q;i++)
    	{
    		cin>>l>>r;
    		int k=logg[r-l+1];
    		cout<<max(rmq[l][k],rmq[r-(unsigned long long)(1<<k)+1][k])<<endl;
    	}
    	return 0;
    }
    

    这属于我没有弄清楚,我原理解成了i+1开始的2^k次个数,然而不是这样的:
    image.png

    AC代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,num[10000],q,l,r;
    int logg[10000],rmq[10000][50];
    int main()
    {
    	cin>>n;
    	logg[1]=0;
    	for(int i=2;i<=10000;i++)
    	logg[i]=logg[i/2]+1;
    	for(int i=0;i<n;i++)
    	{
    		cin>>num[i];
    		rmq[i][0]=num[i];
    	}
    	for(int j=1;j<=18;j++)
    	for(int i=0;i+(1<<j)-1<=n;i++)
    	rmq[i][j]=max(rmq[i][j-1],rmq[i+ (1<<(j-1)) ][j-1]);
    	cin>>q;
    	for(int i=1;i<=q;i++)
    	{
    		cin>>l>>r;
    		int k=logg[r-l+1];
    		cout<<max(rmq[l][k],rmq[r-(1<<k)+1][k])<<endl;
    	}
    	return 0;
    }
    

    PS:

    • for(int i=0;i+(1<<j)-1<=n;i++)这一行中判断部分不能写成i<n,虽然不清楚为什么,但是这样遇到特定数据时控制台直接罢工了……
      若大佬知晓,望打在评论区内。
    • pow貌似不能放进数组的[]中,会报错,所以小编这里替换成了位运算
    • logg其实是因为已经有函数log了,当然直接用log函数应该也可以,记得要上取整,但是不知道为什么就RE了,还是手写吧……
  • 相关阅读:
    我的扑克牌
    oc第二课
    oc入门第一课
    2014年1月3日
    iOS Navigation自定义设置Item
    iOS 在一个TableView内使用不同的Cell
    iOS 获取应用当前定位授权状态
    iOS 8显示应用角标
    Xcode 设置Pch文件
    iOS 8设置CLLocation授权
  • 原文地址:https://www.cnblogs.com/TFLS-gzr/p/13496840.html
Copyright © 2011-2022 走看看