[CF741D] Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [树链剖分]

题意:给定一棵 N N 点有根树,每条树边上有 a a v v 里的某个字母。
求每个子树中最长,满足路径上的字母重排之后可以构成回文串的路径。
N 5 1 0 5 N \le 5*10^5

CF741是单片高性能内补偿运算放大器

显然回文串里面最多只有一个字母出现奇数次,可以用 22 22 位的二进制数来表示每个字母出现次数的奇偶情况。
v a l ( i ) val(i) 为从总根 1 1 i i 路径上每个字母出现次数的奇偶情况(压进二进制数里)。
考虑现在正在求以 x x 为根的子树的答案 A n s Ans
A n s = m a x ( d e p ( u ) + d e p ( v ) 2 d e p ( l c a ( u , v ) ) , v a l ( u ) v a l ( v ) = 2 p , p [ 1 , 22 ] Z    o r    0 , d e p ( l c a ( u , v ) ) d e p ( x ) Ans=max(dep(u)+dep(v)-2*dep(lca(u,v)),val(u)\land val(v)=2^{p,p\in[1,22]\cap Z^*}\;or\;0,dep(lca(u,v))\le dep(x)

数据范围明示复杂度 Θ ( N l o g N ) \Theta(NlogN)

很明显这不是一个点分治题目,不过这个既视感还是挺重的
考虑把路径分为过 r o o t root 和不过 r o o t root
不经过 r o o t root 的交给下面的 l c a ( u , v ) lca(u,v) 去搞,过 r o o t root 的就地解决

r o o t root 的也可以分成两种(虽然不分也行),一种是有一个端点是根的,
另外那种,枚举到某个点 x x 的时候找出满足 v a l ( x ) v a l ( y ) = 2 p , p [ 1 , 22 ] Z    o r    0 val(x) \land val(y)=2^{p,p\in[1,22]\cap Z^*}\;or\;0 d e p ( y ) dep(y) 最大的 y y
只需要枚举 2 p , p [ 1 , 22 ] Z    o r    0 2^{p,p\in[1,22]\cap Z^*}\;or\;0 一共 23 23 种异或结果然后拿 v a l ( x ) val(x) 去对出 v a l ( y ) val(y) 就行了
对于每个 v a l val 记录一个 m a x _ d e p max\_dep
那么可以树剖暴力或者搞树上启发式合并
Θ ( 23 N l o g N ) \Theta(23*NlogN)

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cctype>
using namespace std;
#define add_edge(u,v,w) nxt[++tot]=head[u],to[tot]=v,head[u]=tot,val[tot]=1<<w
int N,t,tot=0,mx=0,mn;
int head[500005]={},nxt[500005]={},to[500005]={},val[500005]={}; //链式前向星 
int dep[500005]={},sum[500005]={},mxdep[5000005]={}; //深度,异或和,异或和最大深度 
int siz[500005]={},son[500005]={},ans[500005]={}; //子树大小,重儿子,答案 
char ch;
void pthDec(int x) //树链剖分 
{
    siz[x]=1; //初始化子树大小为1
    for(int i=head[x];i;i=nxt[i])
    {
        dep[to[i]]=dep[x]+1; sum[to[i]]=sum[x]^val[i]; //更新子节点的深度,到根路径的权值异或和 
        pthDec(to[i]); //递归 
        siz[x]+=siz[to[i]]; //更新子树大小
        if(siz[to[i]]>siz[son[x]])son[x]=to[i]; //维护重儿子 
    }
}
void calc(int x,int root) //计算答案 
{
    mx=max(mx,mxdep[sum[x]]+dep[x]-2*dep[root]); //不到根,xorsum=0
    for(int i=0;i<22;++i)mx=max(mx,mxdep[(1<<i)^sum[x]]+dep[x]-2*dep[root]); //不到根,xorsum=2^p 
    //这两句决定了mxdep要初始化为-inf
    
    if(!(sum[x]^sum[root]))mx=max(mx,dep[x]-dep[root]); //到根,xorsum=0 
    for(int i=0;i<22;++i)if((sum[x]^sum[root])==(1<<i))mx=max(mx,dep[x]-dep[root]); //到根,xorsum=2^p 
    
    for(int i=head[x];i;i=nxt[i])calc(to[i],root); //递归 
}
void modify(int x,bool type) //统计或者抹除贡献 
{
    mxdep[sum[x]]=type?max(mxdep[sum[x]],dep[x]):mn; //更新mxdep 
    for(int i=head[x];i;i=nxt[i])modify(to[i],type); //递归 
}
void dfs(int x,bool keep)
{
    for(int i=head[x];i;i=nxt[i])
    {
        if(to[i]==son[x])continue;
        dfs(to[i],0); //递归轻儿子 
    }
    if(son[x])dfs(son[x],1); //递归重儿子 
    
    mx=max(0,mxdep[sum[x]]-dep[x]); //重儿子,xorsum=0 
    for(int i=0;i<22;++i)mx=max(mx,mxdep[(1<<i)^sum[x]]-dep[x]); //重儿子,xorsum=2^p
    
    for(int i=head[x];i;i=nxt[i])
    {
    	if(to[i]==son[x])continue;
    	calc(to[i],x); modify(to[i],1); //暴力统计轻儿子贡献 
    }
    ans[x]=mx; //保存答案 
    
    if(keep)mxdep[sum[x]]=max(mxdep[sum[x]],dep[x]); //保存贡献 
    else //抹除贡献 
    {
    	for(int i=head[x];i;i=nxt[i])modify(to[i],0); //子节点的 
    	mxdep[sum[x]]=0; //根的 
    }
}
void sumans(int x) //树形dp合并答案 
{
    for(int i=head[x];i;i=nxt[i])
    {
        sumans(to[i]); //递归 
        ans[x]=max(ans[x],ans[to[i]]); //合并 
    }
}
int main()
{
    memset(mxdep,128,sizeof(mxdep)); mn=mxdep[0]; //初始化为极小值-inf
    
    scanf("%d",&N);
    for(int i=2;i<=N;++i)
    {
        scanf("%d %c ",&t,&ch);
        add_edge(t,i,(int)(ch-'a'));
    } //读入,连边 
    pthDec(1); //树剖 
    dfs(1,0);  //暴力统计每个点独立的答案(过每个点的路径) 
    sumans(1); //合并统计答案(把子树的答案合并到根) 
    for(int i=1;i<=N;++i)printf("%d ",ans[i]); //输出 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Estia_/article/details/82890039