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

来源:互联网 发布:前端seo 编辑:程序博客网 时间:2024/05/17 04:45

不正常国家

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 100010using 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
原创粉丝点击