Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths CodeForces - 741D【dsu on tree+异或】

题意:

  给出一棵 \(n\) 个点的树,每条边上有一个字母(\(a\to v\),共 \(22\) 个),对于每一个子树,询问其中最长的,满足:路径上的字符集可以重组成回文字符串的路径的长度。
数据范围:\(1  ≤  n  ≤  5·10^5\)

分析:

  \(dsu\;on\;tree\) 可用于解决不带修改的树上问题。
  其大致过程为:对于每个点 \(v\),先遍历其轻儿子所在的子树,遍历完成后,清除其影响。最后遍历重儿子,保留影响。然后,把点 \(v\)和所有轻儿子的影响加到重儿子上(相当于再一次遍历以点 \(v\) 为根的子树,但没有遍历重儿子所在的子树)。相对于暴力 \(O(n^2)\) 的做法,它第二次遍历的点只有轻儿子。可以证明,其复杂度可以优化到 \(O(nlogn)\),和分块一样是优美的暴力。
  本题的巧妙之处在于异或的运用和状态压缩。每个字母赋予一个 \(2\) 进制位,预处理出每个点到根结点的异或值。对于一条满足条件的路径,所有字母异或之后的结果为 \(0\)\(2^x\) 的形式。对于路径的连个端点 \(u\)\(v\),两者的 \(lca\) 到根节点的路径重复了两次,所以可以抵消。然后对于点 \(v\) 所在子树的满足条件的最长路径,有两种情况。
1.点 \(v\) 在路径上。
2.点 \(v\) 不在路径上,那么只要求出儿子的最大值即可。
本题所说的影响为每种异或值所在的最大深度。
注意初始化时,要赋 \(-inf\),而不能赋 \(0\)

代码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=5e5+5;
const int maxn=1e7;
int son[N],sz[N],depth[N],xr[N],ans[N];
int d[maxn];
vector<int>G[N];
void read(int &x)
{
    x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    x*=f;
}
void dfs1(int v,int d)
{
    sz[v]=1;
    depth[v]=d;
    son[v]=0;
    for(int i=0;i<G[v].size();i++)
    {
        int u=G[v][i];
        xr[u]^=xr[v];
        dfs1(u,d+1);
        sz[v]+=sz[u];
        if(sz[u]>sz[son[v]])
            son[v]=u;
    }
}
void add(int v)
{
    d[xr[v]]=max(d[xr[v]],depth[v]);
    for(int i=0;i<G[v].size();i++)
        add(G[v][i]);
}
void an(int v,int tp)
{
    ans[tp]=max(ans[tp],depth[v]+d[xr[v]]);
    for(int i=0;i<22;i++)
        ans[tp]=max(ans[tp],depth[v]+d[(1<<i)^xr[v]]);
    for(int i=0;i<G[v].size();i++)
        an(G[v][i],tp);
}
void del(int v)
{
    d[xr[v]]=-inf;
    for(int i=0;i<G[v].size();i++)
        del(G[v][i]);
}
void dfs2(int v,bool f)
{
    for(int i=0;i<G[v].size();i++)
    {
        int u=G[v][i];
        if(u==son[v]) continue;
        dfs2(u,false);
    }
    if(son[v])
        dfs2(son[v],true);
    for(int i=0;i<G[v].size();i++)
    {
        if(G[v][i]!=son[v])
            an(G[v][i],v),add(G[v][i]);//为了保证路径一定过点v
    }
    d[xr[v]]=max(d[xr[v]],depth[v]);
    ans[v]=max(ans[v],d[xr[v]]+depth[v]);//cout<<v<<" = "<<ans[v]<<endl;
    for(int i=0;i<22;i++)
        ans[v]=max(ans[v],depth[v]+d[(1<<i)^xr[v]]);//
    ans[v]-=(depth[v]*2);//减去重复的部分
    for(int i=0;i<G[v].size();i++)//点v不在路径中,从儿子节点中找
        ans[v]=max(ans[v],ans[G[v][i]]);
    if(!f)//轻儿子要清空
        del(v);
}
int main()
{
    int n,u;
    char op[5];
    read(n);
    fill(d+1,d+(1<<22),-inf);//注意初始化为-inf
    for(int i=2;i<=n;i++)
    {
        read(u);
        scanf("%s",op);
        G[u].pb(i);
        xr[i]^=(1<<(op[0]-'a'));//每个字母分配二进制的一位
    }
    dfs1(1,0);//for(int i=1;i<=n;i++) cout<<" son="<<son[i];cout<<endl;
    dfs2(1,0);
    for(int i=1;i<=n;i++)
        printf("%d%c",ans[i],i==n?'\n':' ');
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/1024-xzx/p/12601056.html