以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;
}