题目大意:给你n组数据,有m个访问,访问[s,t]区间内第k个大的数
解题报告:刚开始以为很简单,对于每一个访问直接用sort算法,但结果,你懂得,超时了!然后查了很多资料,他们都用二分法+线段树,我看了一下午还是不会,想找个比较简单的,想了半天以及看了别人的代码,我有了思路
解题思路:首先我定义一个结构体,包含输入数据以及它的下标位置。输入数据后先排列,但保持原来下标不变。之后直接从头开始找,当找到一个数它的下标大于等于s并小于等于t时,这时找到第一个数,cnt++;当找到第k个数时,结束循环,输出第K个数
代码:
#include <iostream>
#include <stdio.h>
#include <algorithm>
#define MAXN 100005
using namespace std;
struct data
{
int pos,num;
};
data N[MAXN];
int cmp(data a,data b)
{
return a.num<b.num;
}
int main()
{
int i,p,a,b,k,cnt,n,m;
scanf("%d%d",&n,&m);
for(i=1; i<=n; i++)
{
scanf("%d",&N[i].num);
N[i].pos=i;
}
sort(N+1,N+n+1,cmp);
for(i=1; i<=m; i++)
{
scanf("%d%d%d",&a,&b,&k);
cnt=0;
for(p=1; p<=n; p++)
if(a<=N[p].pos&&N[p].pos<=b)
{
cnt++;
if(cnt==k)
break;
}
printf("%d\n",N[p].num);
}
return 0;
}
顺便附一下别人的代码,我暂时还是疑惑的,先放在这,我以后看了方便,谢谢这位牛人的代码了!!!
二分查找 + 线段树
这里线段树就是query()函数。不懂线段树的童鞋先不要急,你们姑且把它看成是二分就行了。
二分在这道题中是很重要的思想. 先初始化, b[]数组排好序就是a[0][], x=0, y=n-1. 我们先取中间的数num=a[0][mid], where mid=(x+y)/2 来测试, 我们得到num在b[lef...rig]中的排在[rankL, rankR)的位置(注意是左闭右开区间). 如果
A. rankL<=k && k<rankR 那么数num就是所求的数;
否则根据下面更新x或y不断迭代.
B. k >= rankR 那么num较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 赋值x = mid+1;
C. y = mid;
那么num较大, 于是取[x...y]区间的左边, 赋值y = mid;
*/
/*#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int ex = 20;
const int maxn =100104;
// rankL是指示大于等于某数的最小位置, rankR是指示大于某数的最小位置
// 比如在数组(必须升序)1, 3, 3, 5, 6, 查找数3, 那么rankL=1, rankR=3; 查找数4, 那么rankL=rankR=3.
int n, m, lef, rig, k, rankL, rankR; // 在每个查询中, 查询区间b[lef...rig]的第k个数.
// a数组共有ex层, 在每一层内, 我们只对某一段进行排序. 看level_sort()函数对其进行理解.
int a[ex][maxn], b[maxn];
// 提示: 把level_sort改为归并排序可以节省时间
void level_sort(int x, int y, int dep) // 表示在层dep中, 我们对a[dep][x...y]进行排序.
{
if (x == y) return; // 只有一个数, 直接返回; 否则我们需要深入下一层继续排序
sort(a[dep]+x, a[dep]+y+1);
int mid = (x+y) / 2;
level_sort(x, mid, dep+1);
level_sort(mid+1, y, dep+1);
}
// 预处理
void process()
{
for (int i=0; i < ex; i++)
{
memcpy(a[i], b, sizeof(int)*n);
}
level_sort(0, n-1, 0);
}
// binary search, 二分查找.
// 可以把它看成是两个函数合成一个, 用judge来判断.
// if (judge == 0), 那么就返回大于等于某数的最小位置(和rankL相关)
// if (judge == 1), 那么就返回大于某数的最小位置(和rankR相关)
int bin(int x, int y, int num, int dep, int judge)
{
y++;
int ll = x;
while (y > x)
{
int mid = (x+y) / 2;
if (a[dep][mid] - num >= judge) y = mid;
else x = mid+1;
}
return x-ll;
}
// 线段树, 相当于二分. 功能: 求数num在b[l...r]中的位置[rankL, rankR)
// 我们现在处于a[dep][ll...rr]中.
void query(int ll, int rr, int l, int r, int num, int dep)
{
if (ll==l && rr==r) // 因为a[dep][ll...rr]是排好序的, 而我们需要查找num在b[l(=ll)...r(=rr)]的位置, 所以就可以直接二分查找求位置了.
{
rankL += bin(ll, rr, num, dep, 0);
rankR += bin(ll, rr, num, dep, 1);
return;
}
// 否则, 我们需要深入到下一层迭代.
if (r<ll || l>rr) return;
int mid = (ll+rr) / 2;
query(ll, mid, l, min(mid, r), num, dep+1);
query(mid+1, rr, max(l, mid+1), r, num, dep+1);
}
void solve()
{
int x = 0;
int y = n-1;
int mid = (x+y) / 2;
while (true)
{
mid = (x+y) / 2;
rankL=0, rankR=0;
query(0, n-1, lef, rig, a[0][mid], 0); // 求数a[0][mid]的位置[rankL, rankR)
if (rankL<=k && k<rankR) break; // 数a[0][mid]就是所求的数
if (k >= rankR) x = mid+1; // a[0][mid]较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 继续迭代
else y = mid; // a[0][mid]较大, 也就是mid的位置较高, 于是取[x...y]区间的左边, 继续迭代
}
printf("%d\n", a[0][mid]);
}
int main()
{
scanf("%d%d", &n ,&m);
for (int i=0; i < n; i++)
{
scanf("%d", &b[i]);
}
process();
while (m--)
{
scanf("%d%d%d", &lef, &rig, &k);
lef--;
rig--;
k--;
solve();
}
return 0;
}