第十一届 2020年 蓝桥杯 校内模拟赛 第10题 解题思路 C/C++ 线段树+分段查询前m大

一、题意

题意:给出一个长n的正整数序列,找出序列中前m大,并按下标从小到大的顺序输出。(这只是我个人的理解,可能跟出题人真正的意思有偏差!)

二、思路分析

进入我的“法海”吧:

(1)法1:暴力出奇迹之暴力排序;两次排序:1)按值大小对n个元素排序;2)对前m个元素按在原序列中的下标次序排序即可。n规模很大的时候,效率会很低滴! 当然这样应该可以过一些测试用例。

(2)法2:利用线段树维护区间最大值,从而加速底层最值查询 。

线段树结点元数据:

struct node{//线段树节点元数据
	int v,i;//v:数值,i:在序列中的下标
}

建成线段树后分段寻找区间前m大:

首先定义一个大根堆(优先队列),维护数据为:

struct data{//大根堆元数据
	int v,k,s,t;//v:数值,k:该数值在输入序列中的下标,[s,t]:该数值是该区间上的最大值
	data(int v,int k,int s,int t):v(v),k(k),s(s),t(t){}
	bool operator<(const data &a)const{//大根堆比较规则
		return v<a.v;
	}
};

一个容器vector<int> res:存储前m大值在序列中对应的下标 。按以下步骤展开搜索:
            1:找到当前区间[s,t]最大值a[k](最开始[s,t]就是最大区间[1,n]),将其下标k加入res数组;
            2:以当前最大值a[k]为中心划分子区间为:[s, k-1],[k+1,t]。然后,在已建好的线段树中,查询出两个子区间对应的最大值,分别加 入大根堆(维护元数据内容见上);
            3:取出大根堆堆顶元数据data,其data.k加入res。将该堆顶值所在区间([s, k-1]或[k+1,t])作为新区间[s,t],回到步骤 2 。当res.size()=m时,则已找够前m大,跳出查找。

     4:此时res中存放的就是前m大值的对应下标,由于需要按下标次序输出,最后再对res从小到大排一下序,遍历输出即为答案。

三、源程序

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue> 
using namespace std;
#define l(x) (x<<1)
#define r(x) (x<<1|1)
const int N=100001;
const int Inf=1e7+1;
struct node{//线段树节点元数据
	int v,i;
	node(){}
	node(int v):v(v){}
	bool operator<(const node &a)const{
		return v<a.v;
	}
};
struct data{//大根堆元数据
	int v,k,s,t;//v:序列中某个元素,k:该元素对应下标,该元素是哪个区间[s,t]上的最值
	data(int v,int k,int s,int t):v(v),k(k),s(s),t(t){}
	bool operator<(const data &a)const{
		return v<a.v;
	}
};

int n,m;
int a[N];
node d[N<<1];

int read(){
	bool f=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=1;
		ch=getchar();
	}
	int ans=0;
	while(ch>='0'&&ch<='9'){
		ans=ans*10+ch-'0';
		ch=getchar();
	}
	return (f?-ans:ans);
}

//递归建树
void build(int k,int L,int R){
	if(L==R){
		d[k].v=read(),d[k].i=L;
		a[L]=d[k].v;
		return;
	}
	int mid=(L+R)>>1;
	build(l(k),L,mid);//左孩子
	build(r(k),mid+1,R);//右孩子
	d[k]=max(d[l(k)],d[r(k)]);//取两个孩子中的最大值
}

//查询区间最大值,[s,t]目标区间,[L,R]当前查询区间,k当前询问结点编号 
node Q(int k,int s,int t,int L,int R){
	if(R<s||L>t) return node(-Inf);
	if(s<=L&&R<=t) return d[k];
	int mid=(L+R)>>1;
	return max(Q(l(k),s,t,L,mid),Q(r(k),s,t,mid+1,R));
}
vector<int> res;//存放前m大对应下标
priority_queue<data> pq;//大根堆

//区间分割找最值,找够m个即可 
void search(int k,int s,int t){
	if(res.size()==m) return;
	node t1=Q(1,s,k-1,1,n);
	node t2=Q(1,k+1,t,1,n);
	pq.push(data(t1.v,t1.i,s,k-1));pq.push(data(t2.v,t2.i,k+1,t));
	data top=pq.top();
	pq.pop();
	res.push_back(top.k);
	search(top.k,top.s,top.t);
}

void solve(){
	build(1,1,n);
	node t=Q(1,1,n,1,n);//查询[1,n]区间上的最大值
	res.push_back(t.i);//将下标入res
	search(t.i,1,n);
	sort(res.begin(),res.end());
	for(int i=0;i<m;++i)
		cout<<a[res[i]]<<" ";
}

int main(){
	n=read(),m=read();
    solve();
    return 0;
}
/*
给大家献上几组测试用例:

5 3
3 1 2 4 3
3 4 3

8 3
3 9 1 2 6 4 5 7
9 6 7

10 4
3 9 1 2 6 4 5 7 10 2
9 6 7 10

10 4
8 9 1 2 6 4 5 7 10 2
8 9 7 10


15 6
8 9 10 2 6 12 4 5 7 10 2 8 11 3 9
9 10 12 10 11 9
*/

 
给大家献上几组测试用例:

5 3
3 1 2 4 3
3 4 3

8 3
3 9 1 2 6 4 5 7
9 6 7

10 4
3 9 1 2 6 4 5 7 10 2
9 6 7 10

10 4
8 9 1 2 6 4 5 7 10 2
8 9 7 10


15 6
8 9 10 2 6 12 4 5 7 10 2 8 11 3 9
9 10 12 10 11 9
 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值