天天写算法POJ的The k-th Largest Group

本文介绍使用树形数组解决求第k大值问题的两种方法:一种查询复杂度为logn*logn,另一种为logn。通过具体代码实现,详细解析了如何进行合并操作及查找第k大值的过程。
今天写到一个题把我卡住了,用树形数组求第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;
}


logn


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


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值