EOJ 2021.10 E.异或树

E. 异或树
在这里插入图片描述

方法一:先开一个大根堆。然后 d f s dfs dfs一遍, 建出可持久化字典树, 并且每个点把自己和祖先的异或最大值扔进堆。循环 k k k次, 每次从堆中弹出最大值, 假设被弹出的点已经被弹出来了 x x x次, 则在字典树上搜它的第 x + 1 x+1 x+1大的异或值, 再扔进堆里即可。时间复杂度 O ( 30 n + k ( l o g n + 30 ) ) O(30n+k(logn+30)) O(30n+k(logn+30))

1.这道题里面也体现了一个技巧就是做第k大值(k< 1 e 6 1e6 1e6)常见都方法就是维护一个优先队列每次取出最大值然后更新。
2.这道题第x节点都可持久化字典树表示的是从根开始到x节点链上的点组成都字典树。虽然在这边这种方式和查询x节点的子树对应的字典树等价,但实际上这种方法在处理树链问题的时候可以省下树链剖分的一个log

#include<bits/stdc++.h>
using namespace std;
struct
{
    int vis[2];
    int num;
}trie[30000005];
int tot;
int root[5000005];
int insert(int x,int rt)
{
    int Root=++tot;
    int now=root[rt];
    for(int i=30;i>=0;i--)
    ///这边根据范围确定,在此处第一位是(1>>30)也就是2^31
    {
        int ch=(x>>i)&1;

        trie[tot]=trie[now];
        trie[tot].vis[ch]=tot+1;
        trie[tot].num++;

        now=trie[now].vis[ch];
        tot++;
    }
    trie[tot]=trie[now];
    trie[tot].num++;
    return Root;
    ///这样写得原因是方便建树root[x]=insert(a[x],i-1);
}
int query(int x,int k,int rt)
///查询x在root[rt]为根的可持久化字典树中异或第k大
{
    int now=root[rt];
    int ans=0;
    for(int i=30;i>=0;i--)
    {
        int ch=(x>>i)&1;
        if(trie[trie[now].vis[!ch]].num>=k)
        {
            now=trie[now].vis[!ch];
            ans|=(1<<i);
        }
        else
        {
            k-=trie[trie[now].vis[!ch]].num;
            now=trie[now].vis[ch];
        }
    }
    return ans;
}
struct node
{
    int val,id,now;
    bool operator < (const node &ths)const
    {
        if(val!=ths.val)return val<ths.val;
        if(now!=ths.now)return now<ths.now;
        return id<ths.id;
    }
};
int dep[1000005],fa[1000005],a[1000005];
vector<int>v[1000005];
priority_queue<node>q;
void dfs(int x,int pre)
{
    if(pre!=0)
    {
        dep[x]=dep[pre]+1;
        fa[x]=pre;
        q.push({query(a[x],1,fa[x]),x,1});
    }
    root[x]=insert(a[x],pre);
    for(auto to:v[x])if(to!=pre)
    {
        dfs(to,x);
    }
}
vector<int>ans;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;i++)
    {
        int tu,tv;
        scanf("%d%d",&tu,&tv);
        v[tu].push_back(tv);
        v[tv].push_back(tu);
    }
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    dfs(1,0);

    for(int i=1;i<=k;i++)
    {
        node p=q.top();
        q.pop();
        ans.push_back(p.val);
        if(p.now<dep[p.id])q.push({query(a[p.id],p.now+1,fa[p.id]),p.id,p.now+1});
    }
    for(auto x:ans)printf("%d\n",x);
    return 0;
}


方法二:先建立可持久化字典树,然后二分第k大值,随后暴力遍历所有字典树获得答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值