樹狀數組:第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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章