题目大意,大小为n的数列a1-an,m次询问,格式为i,j,k,意思是ai-aj中第k小的数。(n<1e5,m<5e3)
挑战程序设计竞赛上的两种解法。
核心思路都是一样的,设计某种一个数据结构,能快速求出i-j中小于等于x的元素个数,然后二分答案。
平方分割:
将n个元素分成sqrt(n)个部分,每个部分内部都拍好了序,这样一个完整部分内部小于等于x的元素个数可以直接用二分(upper_bound()
)求解,不完整部分也能在sqrt(n)时间内遍历求出。
但在poj上只有一定的概率能AC,TLE的可能性很大,按挑战上的说法,复杂度是O(nlogn+m*sqrt(n)*(logn)^1.5)
所以最大是1.8e8,限时20s,很悬。(submit了20多次才发现一下原来代码没问题,有一定概率TLE,害得我改了好久的代码)
AC(TLE)代码(AC概率相对TLE较小。。。)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
const int MAXN = 2*1e5;
const int b = 1000;
int n, m, a[MAXN], num[MAXN], l, r, k;
vector<int> bucket[MAXN/b];
int cal(int l, int r, int x) //<=x[l,r]区间中<=x元素个数
{
int tl = l, tr = r, ans = 0;
while(tl < tr && tl%b) if(a[tl++] <= x) ++ans;//不完整部分,枚举
while(tl < tr && tr%b) if(a[--tr] <= x) ++ans;
while(tl<tr)//完整部分直接二分求
{
int bb = tl / b;
ans += upper_bound(bucket[bb].begin(), bucket[bb].end(), x) - bucket[bb].begin();
tl += b;
}
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=0; i<n; ++i)
{
scanf("%d", a+i);
bucket[i/b].push_back(a[i]);
num[i] = a[i];
}
sort(num, num+n);
for(int i=0; i<n/b; ++i) sort(bucket[i].begin(), bucket[i].end());
for(int i=0; i<m; ++i)
{
scanf("%d%d%d", &l, &r, &k);//好像现把全部数据一次性读入存下来在运算AC概率更大
--l; --r;//题目数据下标从1开始,这个程序中从0开始
int low = 0, high = n-1;//二分的是有序数组num的下标,而不是答案的值
int mid;
while(low < high)//二分
{
mid = (high + low)/2;
int x = num[mid];
if(cal(l, r+1, x) < k) low = mid+1;
else high = mid;
}
printf("%d\n", num[low]);
}
return 0;
}
下面是一定能AC的线段树解法
和普通线段树类似,但每个节点都是一个有序数组这样每个区间都可分成多个子区间再对他们都进行二分求小于x的元素个数
复杂度是O(nlogn + m*(logn)^3)最多1e7
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN = 1e5 + 100, INF = 1e9*2;
int n, m, a[MAXN], I, J, K, num[MAXN];
vector<int> dat[MAXN<<2];
#define ls l, m, rt<<1
#define rs m+1, r, rt<<1|1
#define defm int m = (l+r)>>1
void pushUp(int rt)
{
dat[rt].resize(dat[rt<<1].size()+dat[rt<<1|1].size());//挑战上没有这一句,能过编译但运行时会奔溃,加上这句就好了,因为dat[rt]的大小是0,merger似乎并不会让vector自动申请内存,所以吧dat[rt]的大小用resize设置成需要的大小就好了
//merger合并两个有序序列成一个有序序列
merge(dat[rt<<1].begin(), dat[rt<<1].end(), dat[rt<<1|1].begin(), dat[rt<<1|1].end(), dat[rt].begin());
}
void init(int l, int r, int rt)
{
if(l == r)
{
dat[rt].push_back(a[l]);
return;
}
defm;
init(ls);
init(rs);
pushUp(rt);
}
int query(int L, int R, int x, int l, int r, int rt)
{
if(R < l || L > r) return 0;//完全不在询问区间
if(L <= l && r <= R) return upper_bound(dat[rt].begin(), dat[rt].end(), x) - dat[rt].begin();//完全在询问区间
defm;
return query(L, R, x, ls) + query(L, R, x, rs);//部分在询问区间
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i) scanf("%d", a+i), num[i] = a[i];
init(1, n, 1);
sort(num+1, num+n+1);
for(int i=0; i<m; ++i)
{
scanf("%d%d%d", &I, &J, &K);
int low = 1, high = n, mid;
while(low < high)
{
mid = (low + high)/2;
if(query(I, J, num[mid], 1, n, 1) < K) low = mid + 1;
else high = mid;
}
printf("%d\n", num[low]);
}
return 0;
}