您的位置:首页 > 其它

noip模拟赛 第k大区间

2017-10-20 21:14 253 查看
【问题描述】

定义一个长度为奇数的区间的值为其所包含的的元素的中位数。

现给出n个数,求将所有长度为奇数的区间的值排序后,第K大的值为多少。

【输入】

输入文件名为kth.in。

第一行两个数n和k

第二行,n个数。(0<=每个数<231)

【输出】

输出文件名为kth.out。

一个数表示答案。

【输入输出样例】

kth.in

kth.out

4 3

3 1 2 4

2

【样例解释】

[l,r]表示区间l~r的值

[1,1]:3

[2,2]:1

[3,3]:2

[4,4]:4

[1,3]:2

[2,4]:2

【数据说明】

对于30%的数据,1<=n<=100;

对于60%的数据,1<=n<=300

对于80%的数据,1<=n<=1000

对于100%的数据,1<=n<=100000, k<=奇数区间的数

分析:有点难想的一道题.

看到第k大,就应该想到要二分.二分x,接下来的任务就是找有多少个区间的值>=x.既然是中位数>=x,那么比x大的数在区间中肯定占了一半以上的数量,那么开一个数组sum[i]表示1~i中有多少个数>=x.一个区间只有2*(sum[r] - sum[l - 1]) > r - (l - 1).接下来就是常见的套路了,把结构相同的放在一起:2*sum[r] - r > 2*sum[l - 1] - (l - 1),换个元,另b[i] = 2*sum[i] - i,问题就转化成了有多少个j满足j < i && b[j] < b[i],树状数组维护一遍就可以了.由于区间长度为奇数,所以要开两个树状数组分别记录奇数和偶数的答案.

第k小/大用二分,式子一定要变形,相同结构放一起,换元之后再求解.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;

ll n, k, a[100010], b[100010], l, r, ans, c[2][300010];
long long anss;

long long query(ll x, ll id)
{
long long res = 0;
while (x)
{
res += c[id][x];
x -= x & (-x);
}
return res;
}

void add(ll x, ll id)
{
while (x <= 200100)
{
c[id][x]++;
x += x & (-x);
}
}

ll cal(ll p)
{
b[0] = 0;
memset(c, 0, sizeof(c));
for (int i = 1; i <= n; i++)
{
b[i] = b[i - 1];
if (a[i] >= p)
b[i]++;
}
for (int i = 0; i <= n; i++)
b[i] = 2 * b[i] - i + 100010;
anss = 0;
for (int i = 0; i <= n; i++)
{
anss += query(b[i] - 1, ((i & 1) + 1) % 2);
add(b[i], i & 1);
}
return anss;
}

int main()
{
scanf("%lld%lld", &n, &k);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
r = max(r, a[i]);
}
l = 0;
while (l <= r)
{
ll mid = (l + r) >> 1;
if (cal(mid) >= k)
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
printf("%lld\n", ans);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: