CF741D——树上启发式合并+状压优化

题目链接:https://www.luogu.com.cn/problem/CF741D

题目描述 

一棵根为1 的树,每条边上有一个字符(a-v共22种)。 一条简单路径被称为Dokhtar-kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串。 求每个子树中最长的Dokhtar-kosh路径的长度。

给你n个点构成的一棵树,树里面的每一条边有一个权值,求出每个子树里面能通过重排构成回文串的最大路径长度

Input

4
1 s
2 a
3 s

Output

3 1 1 0

题解

比较好的一道dsu on tree的题,而且还是发明者专门为这个算法出的一道题。

首先我们需要从一个点来入手,容易发现字符只有22种,比较少,而满足重排后能构成回文串的条件是:每个字符出现次数都为偶数或出现次数为奇数的字符仅有一个。

因此我们可以考虑将其压缩成0表示出现次数为偶数,1表示出现次数为奇数。

而对于一个路径上的字符,我们只需考虑每个字符的出现次数,这样我们巧妙地把问题状压了一下,用一个整数就可以表达出所有符合条件的状态:0 或者 (1<<i),0<=i<=21,总共22种。

之后我们可以考虑维护一个值 dis[i] 表示 从 i 到 1 的路径上字符的状态,其实利用异或的性质,dis[i]就是从 1 到 i 路径里面所有字符表示的状态异或的答案。

 同时我们如果知道两个点,怎样求出两个点形成的状态呢,也是利用异或的性质,假设有两个点u和v,dis[u]^dis[v]^dis[lca(u,v)]^dis[lca(u,v)]其实就是答案,简化一下就是 dis[u]^dis[v],因为两个点的lca上的部分会因为异或的性质抵消掉。

那么我们就很容易发现如果两个节点之间形成的路径符合条件的话,那么dis[u]^dis[v] = 0 | (1<<i),1<=i<=21

这里我们还需要在求每个子树的答案维护一个值,即f[dis[i]] 表示在以当前节点为根的子树中状态为dis[i] 的最大深度。

然后我们先按照dsu on tree 套路,先去遍历每一个轻儿子,计算数据但是不保留,之后处理重儿子,计算数据并且保留,最后再计算轻儿子和当前节点对答案的贡献。

具体过程参考代码,时间复杂度O(23nlogn)

代码实现

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define E 2.718281828
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,s,t) for (register int i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
const int INF = 0x3f3f3f3f;
const int N = 5e5+7;
int tot,L[N],R[N],Id[N];//用来维护dfs序
int head[N],cnt;
struct Edge{
    int to,nxt,w;
}e[N];//链式向前星存储树
int deep[N],sz[N],hSon[N];//分别记录节点的深度,大小,以及重儿子
int ans[N],visited[N];//记录每个点的答案和标记是否访问过
int f[N*10],dis[N];
//dis[i]表示从1(根节点)到 i 点路径上的状态
//f[i]表示在以当前节点为根的子树中状态为 i 的最大深度
void addEdge(int u,int v,int w){//建边
    e[++cnt]=(Edge){v,head[u],w};
    head[u]=cnt;
}
void dfs1(int u,int f){//这一次dfs是预先求出每个节点的深度,重儿子,以及每个节点的dfs序,常规操作
    deep[u]=deep[f]+1;
    sz[u]=1;
    L[u]=++tot;
    Id[tot]=u;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        dis[v]=dis[u]^e[i].w;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[hSon[u]]<sz[v]) hSon[u]=v;
    }
    R[u]=tot;
}
void calc(int u){
    //这里f[dis[u]]表示在以u为根的子树中状态为dis[u]的最大深度
    if(f[dis[u]]) ans[u]=max(ans[u],f[dis[u]]-deep[u]);
    rp(i,0,21) if(f[dis[u]^(1<<i)]) ans[u]=max(ans[u],f[dis[u]^(1<<i)]-deep[u]);
    //这里看着有点不好理解,其实就是利用了异或的性质,我们首先需要考虑以u为起点对答案的贡献
    //因为dis[u]表示从1到u的状态,所以如果符合条件一定满足 x^dis[u]=0||(1<<i) -> x = dis[u] ^ 0||(1<<i); 
    f[dis[u]]=max(f[dis[u]],deep[u]);//更新和维护f数组
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==hSon[u]||visited[v]) continue;
        rp(j,L[v],R[v]){//这里考虑的是以u为中间点的情况,其实和上面情况类似
            int x=Id[j];
            //其实就是看另一边是否出现相同满足条件状态,出现的话更新答案
            if(f[dis[x]]) ans[u]=max(ans[u],f[dis[x]]+deep[x]-2*deep[u]);
            rp(k,0,21) if(f[dis[x]^(1<<k)]) ans[u]=max(ans[u],f[dis[x]^(1<<k)]+deep[x]-2*deep[u]);
        }
        rp(j,L[v],R[v]) f[dis[Id[j]]]=max(f[dis[Id[j]]],deep[Id[j]]);//维护f数组 
    }
}
void dfs2(int u,int keep){//keep为 1 表示处理并保留数据,否则表示处理但不保留数据
    for(int i=head[u];i;i=e[i].nxt){//先对轻儿子进行处理答案,但是不保留数据
        int v=e[i].to;
        if(v==hSon[u]) continue;
        dfs2(v,0);
        ans[u]=max(ans[u],ans[v]);//更新答案,因为有可能最大长度的链不以当前节点为根节点
    }
    if(hSon[u]) dfs2(hSon[u],1),ans[u]=max(ans[u],ans[hSon[u]]),visited[hSon[u]]=1;//对重儿子进行类似处理,但是保留数据并且标记
    calc(u);//更新答案
    if(hSon[u]) visited[hSon[u]]=0;
    if(keep==0) rp(i,L[u],R[u]) f[dis[Id[i]]]=0;//如果为轻儿子,则清空数据
}
int main(){
    int n=read();
    rp(i,2,n){
        int x;char s[10];
        scanf("%d%s",&x,s);
        addEdge(x,i,1ll<<(s[0]-'a'));
    }
    dfs1(1,1);
    dfs2(1,1);
    rp(i,1,n) printf("%d ",ans[i]);
    return 0;
}
发布了342 篇原创文章 · 获赞 220 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43472263/article/details/104147207