[HNOI2012]永无乡【splay】

题目链接


  好题哇,学会了什么叫做splay树的合并,这道题很容易会去想到使用并查集来解,当然,我之前写过并查集加上线段树合并来做这道题的,现在换种想法,也是学了splay不久的缘故,写起来磕磕碰碰的。

  这道题让我也更加的懂了关于splay的根的一些设置,考虑到对于splay的根有了更多的了解,譬如我们通过多个根来建立多棵splay树,根的指向是根上的节点(因为这道题的每个点初始都是有自己的值的),但是为了区分开来,我们对于根节点要进行处理,链接在根上的节点的值是">N"的,也就是说一开始就对这些实名节点"+N",这是为什么呢?为了避免在链接在该点的下个点的时候出现了重复现象,就是对于根/节点区分开来的作用。(但是这道题好像并没有体现的那么明显)

  然后就是怎么做的问题,先大致想一下需要最多时间复杂度的合并操作,为了使合并操作的时间复杂度尽可能低的,我们每次把小的splay树合并进大的splay树去,那么就可以将时间复杂度降至O(NlogN)左右。

  然后就是插入操作了,将两棵splay树合并,我们需要的是把小树的每个点都合并上大树,但是为了让每个节点都是合并进去,而避免一次合并太多节点,我们考虑到,从叶子节点往上合并进另一棵splay树去。

  具体看一下代码。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int maxN = 1e5 + 7;
int N, M, Q, root[maxN], fa[maxN], tot, Rank[maxN];
int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); }
struct node
{
    int ff, val, size, ch[2];
    node() { ff = val = size = ch[0] = ch[1] = 0; }
}t[maxN<<2];
void pushup(int rt) { t[rt].size = t[t[rt].ch[0]].size + t[t[rt].ch[1]].size + 1; }     //因为是价值故不同,所以不会有重叠的
void Rotate(int x)
{
    int y = t[x].ff, z = t[y].ff;
    int k = t[y].ch[1] == x;
    t[z].ch[t[z].ch[1] == y] = x;
    t[x].ff = z;
    t[y].ch[k] = t[x].ch[k^1];
    t[t[x].ch[k^1]].ff = y;
    t[x].ch[k^1] = y;
    t[y].ff = x;
    pushup(y);  pushup(x);
}
void Splay(int x, int goal)
{
    while(t[x].ff != goal)
    {
        int y = t[x].ff, z = t[y].ff;
        if(z != goal) (t[z].ch[0] == y) ^ (t[y].ch[0] == x) ? Rotate(x) : Rotate(y);
        Rotate(x);
    }
    if(goal <= N) root[goal] = x;
}
void insert(int x, int tree)    //tree指的是移动到哪棵树的底下节点
{
    int u = root[tree], ff = tree;
    while(u && t[u].val != x)
    {
        ff = u;
        u = t[u].ch[x>t[u].val];
    }
    u = ++tot;
    if(ff > N) t[ff].ch[x>t[ff].val] = u;
    t[u].ff = ff;
    t[u].val = x;
    t[u].size = 1;
    Splay(u, tree);
}
void dfs(int u, int tree)   //计划去合并到哪棵树上去
{
    if(t[u].ch[0]) dfs(t[u].ch[0], tree);
    if(t[u].ch[1]) dfs(t[u].ch[1], tree);
    insert(t[u].val, tree);
}
void Merge(int x, int y)
{
    int u = fid(x), v = fid(y);
    if(u == v) return;
    if(t[root[u]].size > t[root[v]].size) swap(u, v);
    fa[u] = v;
    dfs(root[u], v);
}
int Kth(int x, int tree)
{
    int u = root[tree];
    if(t[u].size < x) return -1;
    while(true)
    {
        int y = t[u].ch[0];
        if(x > t[y].size + 1)
        {
            x -= t[y].size + 1;
            u = t[u].ch[1];
        }
        else
        {
            if(t[y].size >= x) u = y;
            else return t[u].val;
        }
    }
}
char op[3];
int main()
{
    scanf("%d%d", &N, &M);
    for(int i=1; i<=N; i++) fa[i] = i;
    tot = N << 1;
    for(int i=1, x; i<=N; i++)
    {
        scanf("%d", &x);
        Rank[x] = i;
        root[i] = i + N;
        t[N+i].val = x;    t[N+i].size = 1;    t[N+i].ff = i;
    }
    for(int i=1, u, v; i<=M; i++)
    {
        scanf("%d%d", &u, &v);
        Merge(u, v);
    }
    scanf("%d", &Q);
    int x, y, key;
    while(Q--)
    {
        scanf("%s", op);
        scanf("%d%d", &x, &y);
        if(op[0] == 'Q')
        {
            key = Kth(y, fid(x));
            if(key ^ -1) key = Rank[key];
            printf("%d\n", key);
        }
        else Merge(x, y);
    }
    return 0;
}
/*
5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3
*/

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值