今天写到一个题把我卡住了,用树形数组求第k大值。一时想不明白,搜了一下发现了这个题的两种做法,一个是logn*logn查询复杂度的,一个是logn复杂度的,都是查找第k大组。不过网上的代码注释也太不负责任了,我看了一个小时都没看明白,最后终于看懂了他的大概意思,我在这里翻译一波
#include <stdio.h>
#define MAXN 300000
int a[MAXN], c[MAXN], f[MAXN];
int n, m;
int lowbit(int x)
{
return x & -x;
}
int find(int x)
{
if (x != f[x])
f[x] = find(f[x]);
return f[x];
}
void add(int x, int num)
{
for ( ; x <= n; x += lowbit(x))
c[x] += num;
}
int sum(int x)
{
int sum = 0;
for ( ; x > 0; x -= lowbit(x))
sum += c[x];
return sum;
}
int main()
{
int i, num, cmd, x, y, k, l, r;
scanf("%d%d", &n, &m);
for (i = 1; i <= n; i++)
f[i] = i;
for (i = 1; i <= n; i++)
a[i] = 1;
add(1, n);//;//i表示的是第几组,和上面的f【i】相对应,a[i]表示的是猫的个数
num = n;
for (i = 1; i <= m; i++)
{
scanf("%d", &cmd);
if (cmd == 0)
{
scanf("%d%d", &x, &y);
x = find(x);
y = find(y);
if (x == y)
continue;
add(a[x], -1);
add(a[y], -1);//这个组猫的数量所索引的那个组数-1,也就是原来有5个猫的组有两组,我减了1,那么就成了1了
add(a[y] = a[x] + a[y], 1);//把加和后对应的数量的组的数量加一,比如10之猫的组原来是0个,现在我得到的a[y]==10,那么数量就为1
f[x] = y;
num--;//合并集合
}
else
{
scanf("%d", &k);
k = num - k + 1;
l = 1;
r = n;//二分逼近求第k大值,就是求第num - k + 1小的值
while (l <= r)
{
int mid = (l + r) / 2;
if (sum(mid) >= k)//注意这里是>=,因为是求第num - k + 1小的,所以尽量往左逼近
r = mid - 1;
else
l = mid + 1;
}
printf("%d\n", l);
}
}
return 0;
}
#include <stdio.h>
#define MAXN 300000
int a[MAXN], c[MAXN + 5], f[MAXN];
int n, m;
int lowbit(int x)
{
return x & -x;
}
int find(int x)
{
if (x != f[x])
f[x] = find(f[x]);
return f[x];
}
void add(int x, int num)
{
for ( ; x <= MAXN; x += lowbit(x))
c[x] += num;
}
int sum(int x)
{
int sum = 0;
for ( ; x > 0; x -= lowbit(x))
sum += c[x];
return sum;
}
/*
求第K小的值。a[i]表示值为i的个数,c[i]当然就是管辖区域内a[i]的和了。
神奇的方法。不断逼近。每次判断是否包括(ans,ans + 1 << i]的区域,
不是的话减掉,是的话当前的值加上该区域有的元素。
注意MAXN是更新到的最大值,如果上面只更新到n的话取n就行了。
乍一看循环的量是常数,难道是O(1)的吗?实际上i应该遍历到LogN,所以该算法是LogN的。比线段树、平衡树代码量少多了。
*/
int find_kth(int k)
{
int ans = 0, cnt = 0, i;
for (i = 20; i >= 0; i--)
{
ans += (1 << i);
if (ans >= MAXN|| cnt + c[ans] >= k)
ans -= (1 << i);
else
cnt += c[ans];
}
return ans + 1;
}
int main()
{
int i, num, cmd, x, y, k, l, r;
scanf("%d%d", &n, &m);
for (i = 1; i <= n; i++)
f[i] = i;
for (i = 1; i <= n; i++)
a[i] = 1;
add(1, n);//a[i]表示组内有i只猫的组数
num = n;
for (i = 1; i <= m; i++)
{
scanf("%d", &cmd);
if (cmd == 0)
{
scanf("%d%d", &x, &y);
x = find(x);
y = find(y);
if (x == y)
continue;
add(a[x], -1);
add(a[y], -1);
add(a[y] = a[x] + a[y], 1);
f[x] = y;
num--;//合并集合
}
else
{
scanf("%d", &k);
printf("%d\n", find_kth(num - k + 1));//第k大就是第num - k + 1小的
}
}
return 0;
}
本文介绍使用树形数组解决求第k大值问题的两种方法:一种查询复杂度为logn*logn,另一种为logn。通过具体代码实现,详细解析了如何进行合并操作及查找第k大值的过程。
1万+

被折叠的 条评论
为什么被折叠?



