【NOIP2017提高A组模拟9.14】生命之树 trie+启发式合并

题目

做法

先给这一颗树按照dfs序重新编号,可以发现一个点对它的某一个父亲的贡献就是其与所有编号小于他的点的贡献和,那么我们可以考虑从叶子节点开始,逐步往上计算每一个点的答案
考虑建一些trie树,把二进制的每一个位挂在trie的一些节点上,那么我们就可以在遍历trie的同时把贡献式中的乘法转换为加法
假设我们现在做到点i,那么我们就要把i点的儿子们对应的trie树合并,考虑用启发式合并,在合并的同时我们可以计算不同子树之间的答案,最后我们再把i点加进trie里并计算贡献就可以了

贴代码

#include
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define cs 17
#define ll long long
using namespace std;

const int maxn=7e5+5;

ll root[maxn],a[maxn],va[maxn],be[maxn],ed[maxn],pd[maxn][cs+4][2],fa[maxn];
ll c[maxn][30],fi[maxn],ne[maxn],dui[maxn],qc[maxn],s[maxn*2],go[maxn],ge[maxn][2],size[maxn];
ll ans[maxn],mk[20],tot;
ll i,j,k,l,m,n,x,y,z,now,nex,ct,sc,q,no,qq;
char ch;

void add(ll x,ll y){
    if (fi[x]==0) fi[x]=++now; else ne[qc[x]]=++now;
    qc[x]=now; dui[now]=y;
}
void dfs(ll x,ll y){
    ll i;
    fo(i,0,cs){
        l=1>>(z-1);
        tot=tot+mk[i]*(pd[x][i][0]*pd[y][i][1]+pd[x][i][1]*pd[y][i][0]);
        pd[x][i][0]+=pd[y][i][0];
        pd[x][i][1]+=pd[y][i][1];
    }
    fo(i,1,26) if (c[y][i]>0){
        if (c[x][i]==0) c[x][i]=c[y][i]; else dfs(c[x][i],c[y][i]);
    }
}
void work1(ll x){
    size[x]=1;
    ll i=fi[x];
    while (i){
        if (size[dui[i]]>0){
            i=ne[i];
            continue;
        }
        fa[dui[i]]=x;
        work1(dui[i]);
        size[x]+=size[dui[i]];
    }
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%lld",&n);
    fo(i,1,n) scanf("%lld",&va[i]);
    fo(i,1,n){
        be[i]=ed[i-1]+1; ed[i]=be[i]-1;
        ch=getchar();
        while (ch<'a' || ch>'z') ch=getchar();
        while (ch>='a' && ch<='z'){
            s[++ed[i]]=ch-96; ch=getchar();
        }
    }
    mk[0]=1; fo(i,1,cs) mk[i]=mk[i-1]*2; 
    fo(i,1,n-1){
        scanf("%lld%lld",&x,&y);
        add(x,y); add(y,x);
        go[x]++; go[y]++;
    }
    now=0;
    work1(1);
    fo(i,1,n) if (go[i]==1 && i!=1){
        ge[++now][0]=i;
        root[i]=++ct; x=ct;
        fo(j,be[i],ed[i]){
            if (! c[x][s[j]]) c[x][s[j]]=++ct;
            x=c[x][s[j]];
            y=va[i]; z=0;
            fo(k,0,cs){
                pd[x][z++][y%2]++;
                y=y/2;
            }
        }
    }
    no=ct;
    sc=0;
    while (true){
        nex=0;
        fo(j,1,now){
            y=ge[j][sc];
            if (root[y]==0){
            i=fi[y]; x=0;
            while (i){
                if (dui[i]==fa[y]){
                    i=ne[i];
                    continue;
                }
                if (size[dui[i]]>size[x]) x=dui[i];
                i=ne[i];
            }
            i=fi[y];
            root[y]=root[x];
            while (i){
                ans[y]=ans[y]+ans[dui[i]];
                if (dui[i]==x || dui[i]==fa[y]){
                    i=ne[i];
                    continue;
                }
                z=1;
                tot=0;
                dfs(root[y],root[dui[i]]);
                ans[y]+=tot;
                i=ne[i];
            }
            x=root[y];
            fo(z,be[y],ed[y]){
                if (! c[x][s[z]]) c[x][s[z]]=++no;
                x=c[x][s[z]];
                q=va[y]; i=0;
                fo(k,0,cs){
                 ans[y]=ans[y]+mk[k]*pd[x][k][1^(q%2)];
                 q=q/2;
                }
                q=va[y];
                fo(k,0,cs){
                    pd[x][i++][q%2]++;
                    q=q/2;
                }
            }
            }
            go[fa[y]]--;
            if (go[fa[y]]<=1 && fa[y]>0){
                if (fa[y]==1 && go[fa[y]]==1) continue;
                ge[++nex][sc^1]=fa[y];
            }
        }
        now=nex;
        if (nex==0) break;
        sc^=1;
    }
    fo(i,1,n) printf("%lld\n",ans[i]);
    return 0;
}

你可能感兴趣的:(启发式合并)