树状数组:第K大值
以POJ 2985为例,具体的写在程序里。思路都是基于二分的思想。
下面是(LogN)^2的方法
/* 题意:某人养了很多猫,他会把一些猫合并成一组,并且会询问第k大组有几只猫 算法:处理集合用并查集,动态更新第K值用树状数组,具体的看注释 2011-07-21 19:59 */ #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);//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); 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; }
下面是LogN的方法
/* 题意:某人养了很多猫,他会把一些猫合并成一组,并且会询问第k大组有几只猫 算法:处理集合用并查集,动态更新第K值用树状数组,具体的看注释 2011-07-21 20:42 */ #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; }