线段树合并——bzoj 2733 [HNOI2012]永无乡

真正的线段树合并的裸题。

题目大意:

原题链接.

每个点的有个类似分数的东西,互不相同。

每次可以加一条双向边(x,y)。

询问x点所在联通块第k的点。

讲一讲牛逼的启发式合并。

开线段树,每次做并查集合并的时候找到点数少的那一个,一个一个把点提出来,加到大的线段树里。

时间复杂度O(n log^2 n)。

线段树合并就不讲了,前一篇博客有讲,10^5跑了2s,常数爆炸。

#include<cstdio> 
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 1e5 + 5;

char ch[2];
int n, m, x, y, k, Q, w[N], r[N], f[N], rank[N];

int tot;
struct node {
    int l, r, s;
} a[N * 100];

int find(int x) {return f[x] == x ? x : (f[x] = find(f[x]));}

void add(int &i, int x, int y, int l, int c) {
    a[++ tot] = a[i]; i = tot;
    if(x == y) { a[i].s += c; return;}
    int m = x + y >> 1;
    if(l <= m) add(a[i].l, x, m, l, c); else add(a[i].r, m + 1, y, l, c);
    a[i].s = a[a[i].l].s + a[a[i].r].s;
}

void merge(int &i, int x, int y) {
    if(!x) {i = y; return;}
    if(!y) {i = x; return;}
    i = ++ tot;
    merge(a[i].l, a[x].l, a[y].l);
    merge(a[i].r, a[x].r, a[y].r);
    a[i].s = a[a[i].l].s + a[a[i].r].s;
}

int find(int i, int x, int y, int k) {
    if(x == y) return x;
    int m = x + y >> 1;
    return a[a[i].l].s >= k ? find(a[i].l, x, m, k) : find(a[i].r, m + 1, y, k - a[a[i].l].s);
}

void bin(int x, int y) {
    if(find(x) != find(y))
        merge(r[f[y]], r[f[x]], r[f[y]]), f[f[x]] = f[y];
}

int main() {
    scanf("%d %d", &n, &m);
    fo(i, 1, n) f[i] = i;
    fo(i, 1, n) scanf("%d", &w[i]);
    fo(i, 1, n) rank[w[i]] = i;
    fo(i, 1, n) add(r[i], 1, n, w[i], 1);
    fo(i, 1, m) {
        scanf("%d %d", &x, &y);
        bin(x, y);
    }
    for(scanf("%d", &Q); Q; Q --) {
        scanf("%s", ch);
        if(ch[0] == 'B') {
            scanf("%d %d", &x, &y);
            bin(x, y);
        } else {
            scanf("%d %d", &x, &k);
            y = find(x);
            if(a[r[y]].s >= k) {
                printf("%d\n", rank[find(r[y], 1, n, k)]);
            } else printf("-1\n");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值