不正常国家 (树链剖分 可持久化Trie 启发式合并)

不正常国家

9.27

思路:
区间异或最值,应该就可以想到可持久化Trie了,区间异或最值的参考题解
我们找出dfs序,在dfs序上建可持久化Trie,root[i]就是一颗维护dfs序上[1,i]的Trie树。
我们考虑对于i点求它的ans,由于我们要保证选择的两个点的lca就是i,所以只有两种情况,一种就是其中一个点为i,第二种就是两个点在两个不同的子树里面,考虑把i单独看做一个部分,然后一个子树单独看做一个部分,那么这两个点就在不同的两个部分中。
所以半暴力的做法就来啦,对于一个点i,把i加入Trie中,然后for一个子树中的每个点在Trie里面跑(求出一个点是i,一个点在这个子树里面的最大异或)然后就把这颗子树加入到Trie里面,然后for下一个子树,拿这个子树中的每个点在Trie里面跑(求出一个点是i或在之前的子树中,一个点在这个子树里面的最大异或)然后再把这颗子树加入到Trie里面,接下来类似之前的操作。
这样就处理了两个点在不同的两个部分中的所有情况。
但是会有一些问题,
第一个就是我们要求的是异或和最大的一条路径(而不是两个点),怎么办呢?
考虑把每个点到1(root)的路径异或和作为每个点的权值,这样我们枚举一个u点,要找Trie树中一个值与u的权值异或起来最大,就直接把u的权值异或lca的权值放在Trie里面跑。(u点到1的路径异或和.异或上.v点到1的路径异或和.异或上.lca(u,v)的权值就是u到v路径的异或和)。
第二个就是怎么加点到Trie里面。
因为考虑到同样的一棵Trie我们会用到多次,所以可持久化Trie,我们都是一棵子树一棵子树整体加入的,而子树在dfs序上是连续的一段,这就解释了我们为什么要在dfs序上维护Trie。然后我们就不用插入了。当我们需要把i加入一个空的Trie时,就在[l,r]的Trie中找就好了(l=in[i],r=out[i]),我们要把i的一个根节点是v的子树加入Trie时,就把r改为out[v]就好了(以dfs找子节点的时候,i和v所在的子树在dfs序上是连续的一段)。
问什么说这是个半暴力做法呢?因为对于每一个节点我们会for完它的所有子树,竟然是n^2的,还不如写暴力呢。。。
忽然风雨大作,我们发现第一次加入Trie的部分是不用被for的!!!
那么我们把最大的部分(最大的一颗子树)最先放到Trie里面不就好啦!!!
但是由于我们的Trie是按照dfs序建的,所以我们加入Trie的顺序也必须满足dfs序,所以考虑树链剖分,重儿子所在的子树就是最大的一个子树,而且dfs序上从i往后的第一棵子树就是重儿子所在的子树,这样就是启发式合并了。
如果有一个子树很大,越大就越优(可以不for它),那么最坏情况就是一个平衡的树,这样就保证了时间复杂度是32*nlog的级别。
特别稳!

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long 
#define N 100010
using namespace std;

int n, m, idc=0, timex;
int a[N], head[N], seq[N<<1], vis[N], in[N], out[N], fa[N], re[N], dis[N]; 
int sz[N], son[N];
struct Edge{
    int to, nxt;
}ed[N<<1];

inline void adde (int u, int v){
    ed[++idc].to = v;
    ed[idc].nxt = head[u];
    head[u] = idc;
}

inline void dfs( int u, int f ){
    fa[u] = f; sz[u] = 1;
    vis[u] = 1;
    int k = head[u];
    while( k > 0 ){
        if(ed[k].to != f){
            dis[ed[k].to] = dis[u] ^ a[ed[k].to];//处理v到root的路径的异或和 
            dfs(ed[k].to, u); sz[u] += sz[ed[k].to];
            if(sz[son[u]]<sz[ed[k].to]) son[u] = ed[k].to;
        }
        k = ed[k].nxt;
    }
}
inline void dfss( int u, int f ){
    in[u] = ++timex; re[timex] = u;//re是in的反函数 
    seq[timex] = dis[u];
    if(son[u])dfss(son[u],u);
    int k = head[u];
    while( k > 0 ){
        if(ed[k].to != f&&ed[k].to!=son[u]){
            dfss(ed[k].to, u);
        }
        k = ed[k].nxt;
    }
    out[u] = timex;
}
struct TRIE{
    int son[2], wgh;
}trie[5000010];

int tot;
int root[N];

inline void Insert(const int pre, int& root_r, const int d, const int step){
    trie[root_r = ++tot] = trie[pre];//每次只更改一条链 
    trie[root_r].wgh++; 
    if(step < 0) return ;
    int p = (d >> step) & 1;
    Insert(trie[pre].son[p], trie[root_r].son[p], d, step-1);
    return ;
}

inline int Query(const int d, const int pre, const int root_r, const int step){
    if(step < 0) return 0;
    int p = (d >> step) & 1;
    if(trie[trie[root_r].son[p^1]].wgh - trie[trie[pre].son[p^1]].wgh)
        return (1<<step) + Query(d, trie[pre].son[p^1], trie[root_r].son[p^1], step-1);
    return Query(d, trie[pre].son[p], trie[root_r].son[p], step-1);
}

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int main(){
    freopen ("irregular.in", "r", stdin);
    freopen ("irregular.out", "w", stdout);
    n = read();
    for(register int i=1; i<=n; i++){
        a[i] = read();
    }
    for(register int i=1; i<n; i++){
        int u = read(), v = read();
        adde(u, v); adde(v, u);
    }
    dis[1] = a[1];
    dfs(1, 1);dfss(1,1);//树链剖分 
    for(register int i=1; i<=timex; ++i) Insert(root[i-1], root[i], seq[i], 30);//在dfs序上建立Trie 
    int l, r, v;
    for(register int i=1; i<=n; ++i){
        int ans = a[i];
        if(fa[re[in[i]+1]] == i){
            v = re[in[i]+1];//找到重儿子 
            l = in[v], r = out[v];
            for(int x=in[i]; x<=in[i]; x++){
                ans = max(ans, Query(seq[x] ^ a[i], root[l - 1], root[r], 30) );//把i放到重儿子的Trie里面跑 
            }
            l = in[i];//把i点加入Trie 
        }
        else{
            printf("%d ", ans);
            continue;
        }
        while(fa[re[out[v]+1]] == i){
            v = re[out[v]+1];//处理其他的轻儿子 
            for(register int x=in[v]; x<=out[v]; x++){//for每个轻儿子 
                ans = max(ans, Query(seq[x] ^ a[i], root[l - 1], root[r], 30) );//在维护的Trie里面跑 
            }
            r = out[v];//把这个子树加入Trie 
        }
        printf("%d ", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值