[CF741D] Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [树链剖分/树上启发式合并]

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

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

显然回文串里面最多只有一个字母出现奇数次,可以用 22 22 22位的二进制数来表示每个字母出现次数的奇偶情况。
v a l ( i ) val(i) val(i)为从总根 1 1 1 i i i路径上每个字母出现次数的奇偶情况(压进二进制数里)。
考虑现在正在求以 x x x为根的子树的答案 A n s Ans 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) Ans=max(dep(u)+dep(v)2dep(lca(u,v)),val(u)val(v)=2p,p[1,22]Zor0,dep(lca(u,v))dep(x)

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

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

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

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值