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

题目

做法

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

贴代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cmath>
#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值