一、题意
题意:给出一个长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