[CF741D]Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths

题目

传送门

题解

由树启发算法发明者出的题。

涉及回文,那么我们来想一下回文的特性:

最多只有一种字符出现奇数次的一堆字符才会被重新排序成为一个回文串。

所以,这道题我们只需要知道,在某一条链上,有多少字符出现奇数次,而偶数次的字符我们可以忽略。

这有点想什么?是不是二进制中的异或运算

那么,我们只需要将每种字符表示为二进制位上的一位,每条边的权值我们可以定义为 \(2^{ch-'a'}\),而两条路的合并就可以直接用 \(\oplus\) 来合并。

接下来怎么处理?

首先想清楚一个东西:对于两条路之间的异或和,我们可以这样计算

\[\begin{aligned} \text{dis}(u,v)&=\text{dis}(u,1)\oplus \text{dis}(\text{lca}_{u,v},1)\oplus \text{dis}(v,1)\oplus \text{dis}(\text{lca}_{u,v},1) \\ &=\text{dis}(u,1)\oplus \text{dis}(v,1) \end{aligned} \]

那么,我们只需要处理出每个点到 \(1\) 的异或路径和,我们就能 \(\mathcal O(1)\) 地处理出两点的路径异或和。

我们把这个数组记为 \(\text{Xor}_u\)

接下来,我们又怎么处理?

假设现在我们处理以 \(u\) 为根的子树,如果一条链从 \(u\) 开始,一直连到他的某一个子树中的点,贪心地,这个点的深度一定是所有满足条件的点中最深的

但是这又引出一个问题,我们怎么找这些符合条件的点,或者说这些符合条件的点需要满足什么要求呢?

由于我们需要求的路径的路径异或和为 \(0\) 或者 \(2^i\),那么这些符合条件的点一定满足下面等式

\[\text{Xor}_u\oplus \text{Xor}_v=0 \]

或者

\[\text{Xor}_u\oplus \text{Xor}_v=2^i \]

由异或和的特性,我们可以解得 \(\text{Xor}_v\) 的值

\[\text{Xor}_v=\text{Xor}_u\space 或\space \text{Xor}_u\oplus2^i \]

由于我们的贪心性质,我们维护一个 \(f_i\) 数组,表示满足 \(\text{Xor}=i\) 的点的最大深度。

这个 \(f_i\) 怎么维护?

可以看一下这段,应该比较好理解

void count(const int u,const int d,const int delta){
    if(delta==1)f[Xor[u]]=Max(f[Xor[u]],d);
    else f[Xor[u]]=0;
    erep(t,u)count(v,d+1,delta);
}

剩下的问题,我们考虑怎么更新一个点 \(u\) 的答案 \(ans_u\)

他有多种途径更新:

  1. 从儿子的子树中来,这种情况 \(ans_u=\min\{ans_u,ans_v\mid v\in son_u\}\)
  2. 考虑和子树中的某个点连接起来,那么 \(ans_u=\min\{ans_u,f_{\text{Xor}_u}-d_u,f_{\text{Xor}_u\oplus 2^i}-d_u\}\)
  3. 考虑跨子树组合成一条链,这个情况我们先不考虑,后面再说;

在具体实现中,我们首先遍历每个子节点(非重儿子)并处理,之后清空子节点得到的 \(f_i\) (就用刚刚的 count() 函数)。

然后处理重儿子,不清空 \(f_i\),然后重新遍历轻儿子,考虑将两个儿子的 \(f_i\) 进行合并,在合并之前,我们可以先遍历一遍轻儿子子树并处理上面第三种情况,即跨子树组合的情况,具体实现可以看一下下面的代码:

int dfs3(const int u,const int d,const int rtd){
    int ret=0;
    if(f[Xor[u]])ret=Max(ret,f[Xor[u]]+d-(rtd<<1));
    rep(i,0,21)if(f[Xor[u]^(1<<i)])
        ret=Max(ret,f[Xor[u]^(1<<i)]+d-(rtd<<1));
    erep(t,u){
        ret=Max(ret,dfs3(v,d+1,rtd));
    }return ret;
}

其中 \(rtd\) 表示 \(u\) 的深度。

在计算完答案之后,我们再将轻儿子子树加入到 \(f_i\) 即可。

代码

#include<bits/stdc++.h>
using namespace std;

#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define uint unsigned int
#define pii pair< int,int >
#define Endl putchar('\n')
// #define int long long
// #define int unsigned
// #define int unsigned long long

#ifdef _GLIBCXX_CSTDIO
#define cg (c=getchar())
template<class T>inline void qread(T& x){
    char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T>inline T qread(const T sample){
    T x=0;char c;bool f=0;
    while(cg<'0'||'9'<c)f|=(c=='-');
    for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
#undef cg
template<class T>void fwrit(const T x){//just short,int and long long
    if(x<0)return (void)(putchar('-'),fwrit(-x));
    if(x>9)fwrit(x/10);
    putchar(x%10^48);
}
#endif
// template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
    inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
    return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}

const int MAXN=5e5;
const int MAXSIZE=1<<22;
struct edge{int to,nxt,w;}e[MAXN+5];
int tail[MAXN+5],ecnt;
inline void add_edge(const int u,const int v,const int w){
    e[++ecnt]=edge{v,tail[u],w};tail[u]=ecnt;
}

int n,ans[MAXN+5];

inline void Init(){
    n=qread(1);
    char ch[5];int fa;
    rep(i,2,n){
        scanf("%d %s",&fa,ch);
        add_edge(fa,i,1<<(ch[0]-'a'));
    }
}

int wson[MAXN+5],sz[MAXN+5],Xor[MAXN+5];

int f[MAXSIZE+5];//记录从根到点异或值为 i 的点的最大深度

void dfs1(const int u){
    sz[u]=1,wson[u]=0;
    erep(t,u){
        Xor[v]=Xor[u]^e[t].w;
        dfs1(v);
        sz[u]+=sz[v];
        if(sz[v]>sz[wson[u]])wson[u]=v;
    }
}

void count(const int u,const int d,const int delta){
    if(delta==1)f[Xor[u]]=Max(f[Xor[u]],d);
    else f[Xor[u]]=0;
    erep(t,u)count(v,d+1,delta);
}

int dfs3(const int,const int,const int);

void dfs2(const int u,const int d){
    erep(t,u)if(v^wson[u]){
        dfs2(v,d+1);
        ans[u]=Max(ans[u],ans[v]);
        /*后面在把 u 加入重儿子中的时候, 也会考虑到将这个点加入轻儿子的链, 所以此处没有必要考虑
        if(cnt[Xor[u]])ans[u]=Max(ans[u],cnt[Xor[u]]-d);
        rep(i,0,21)if(cnt[Xor[u]^(1<<i)])
            ans[u]=Max(ans[u],cnt[Xor[u]^(1<<i)]-d);
        */
        count(v,d+1,-1);
    }
    if(wson[u])dfs2(wson[u],d+1),ans[u]=Max(ans[u],ans[wson[u]]);
    if(f[Xor[u]])ans[u]=Max(ans[u],f[Xor[u]]-d);
    rep(i,0,21)if(f[Xor[u]^(1<<i)])ans[u]=Max(ans[u],f[Xor[u]^(1<<i)]-d);
    f[Xor[u]]=Max(d,f[Xor[u]]);
    erep(t,u)if(v^wson[u]){
        ans[u]=Max(dfs3(v,d+1,d),ans[u]);
        count(v,d+1,1);
    }
}

int dfs3(const int u,const int d,const int rtd){
    int ret=0;
    if(f[Xor[u]])ret=Max(ret,f[Xor[u]]+d-(rtd<<1));
    rep(i,0,21)if(f[Xor[u]^(1<<i)])
        ret=Max(ret,f[Xor[u]^(1<<i)]+d-(rtd<<1));
    erep(t,u){
        ret=Max(ret,dfs3(v,d+1,rtd));
    }return ret;
}

signed main(){
    // ios::sync_with_stdio(false);
    Init();
    dfs1(1);
    dfs2(1,0);
    rep(i,1,n)printf("%d ",ans[i]);
	return 0;
}
posted @ 2020-06-05 09:58  Arextre  阅读(152)  评论(0编辑  收藏  举报