首页 > 其他分享 >D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths (cf741D)

D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths (cf741D)

时间:2023-02-18 22:24:27浏览次数:50  
标签:paths Dokhtar kosh int dfs son dep dp dis

D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths (cf741D)

tag: dsu on tree,dp

题目链接

题意:

给你一棵树,每一条边的权值是'a' 到'v' 的字母,求在每一个点的子树上找一条的简单路径,使该路径包含的字符排序后成为回文串,输出长度即可。

做法:

字母'a'到'v'一共22个字母,摆明了要状压,每一位用来表示某字母的奇偶性,要满足排序后形成回文串,那么至多出现一个1
我们用dis[u]描述从根节点走到1走u的字母情况,dep[u]表示深度,f[dis[u]]表示字母奇偶情况相同时的最大深度
对于u子树中的最长链,有二种可能

  • 最长链不经过u而直接来自某颗儿子的子树中
  • 最长链经过u

我们分析一下第二种可能

  • 最长链的一端为u
    子树中任意节点x的dis[x]一定有经过根节点u,u之前的路径都是一样的,从u到节点x的的路径就是 dis[x]^dis[u]
  • u在最长链之间,x,y分别属于u的不同儿子的子树
    那么从x出发经过u到达y的路径可表示为, dis[x] ^ dis[u] ^ dis[u] ^ dis[y] = dis[x]^dis[y]
    理解了这些就很好写了,我们将问题转化为子树问题,使用启发式合并求解。

写了两版,都有注释可供参考

代码:

#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long 
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+50;
const int mod=998244353;
int sz[N],son[N],dp[N];
int st[N],id[N],dep[N],dis[N],ed[N]; //st、ed描述dfs序的区间,id计入按dfs序排序后的节点,dep表示深度,dis用来描述路径f[dis[u]]-dep[u]
int dfs_time;
int f[(1<<22)+5]; //字母数字范围a~v 22个字母
struct node{
    int v,c;
};
vector<node> g[N];
void predfs(int u,int fa){
    sz[u] = 1;
    st[u] = ++dfs_time;
    id[dfs_time] = u; //其实就是把节点按dfs序排序了
    for(auto e: g[u]){
        if(e.v==fa) continue;
        dep[e.v] = dep[u] + 1;
        dis[e.v] = dis[u]^(1<<e.c); //将经过的路径的权值状压
        predfs(e.v,u);
        sz[u] += sz[e.v];
        if(sz[e.v]>sz[son[u]]) son[u] = e.v;
    }
    ed[u] = dfs_time;
}

void dfs(int u,int fa,int op){
    for(auto e : g[u]){
        if(e.v==son[u]||e.v==fa) continue;
        dfs(e.v,u,0);
        dp[u] = max(dp[u],dp[e.v]); //先树形dp一波,最长链可能在子树当中
    }
    if(son[u]) dfs(son[u],u,1),dp[u]=max(dp[u],dp[son[u]]); 
    
    //接下来算经过u的最长链
    //链经过u和重儿子,因为现在只保留了重儿子的信息
    if(f[dis[u]]) dp[u]=max(dp[u],f[dis[u]]-dep[u]);
	for(int i=0;i<22;i++){
		if(f[dis[u]^(1<<i)]) dp[u]=max(dp[u],f[dis[u]^(1<<i)]-dep[u]);
    }
    f[dis[u]] = max(f[dis[u]],dep[u]);//更新最大深度

    for(auto e: g[u]){
        if(e.v==son[u]||e.v==fa) continue;
        for(int i = st[e.v];i<=ed[e.v];i++){ //遍历子树
            int x = id[i];//当前节点
            if(f[dis[x]]) dp[u] = max(dp[u],f[dis[x]]+dep[x]-2*dep[u]);
			for(int k=0;k<22;k++){
				if(f[dis[x]^(1<<k)]) dp[u] = max(dp[u],f[dis[x]^(1<<k)]+dep[x]-2*dep[u]);
            }
        }
        for(int i = st[e.v];i<=ed[e.v];i++) f[dis[id[i]]] = max(f[dis[id[i]]],dep[id[i]]);//将轻儿子和重儿子合并
    }
    if(!op) for(int i=st[u];i<=ed[u];i++) f[dis[id[i]]]=0; //清楚贡献
    //为什么可以直接清0,虽然我们f[]是多个状态的最大值,但是这些状态都被包括在子树中,所以大胆删就好
}

int main() {
    fst;
    int n,x; cin>>n;
    char c;
    for(int i=2;i<=n;i++){
        cin>>x>>c;
        g[i].push_back({x,c-'a'});
        g[x].push_back({i,c-'a'});
    }
    predfs(1,0);
    dfs(1,0,1);
    for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
    return 0;
}
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long 
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+50;
const int mod=998244353;
int sz[N],son[N],dp[N];
int dep[N],dis[N]; //st、ed描述dfs序的区间,id计入按dfs序排序后的节点,dep表示深度,dis用来描述路径
int dfs_time;
int f[(1<<22)+5]; //字母数字范围a~v 22个字母
struct node{
    int v,c;
};
vector<node> g[N];
void predfs(int u,int fa){
    sz[u] = 1;
    for(auto e: g[u]){
        if(e.v==fa) continue;
        dep[e.v] = dep[u]+1;
        dis[e.v] = dis[u]^(1<<e.c); //将经过的路径的权值状压
        predfs(e.v,u);
        sz[u] += sz[e.v];
        if(sz[e.v]>sz[son[u]]) son[u] = e.v;
    }
}

void update(int u,int fa,int op){ //op为0删除,op为1取最大
    f[dis[u]] = op ? max(f[dis[u]],dep[u]) : 0;
    for(auto e: g[u]){
        if(e.v==fa) continue;
        update(e.v,u,op);
    }
}

void count(int u,int fa,int tf){

    if(f[dis[u]]) dp[tf] = max(dp[tf],f[dis[u]]+dep[u]-2*dep[tf]);
    for(int k=0;k<22;k++){
        if(f[dis[u]^(1<<k)]) dp[tf] = max(dp[tf],f[dis[u]^(1<<k)]+dep[u]-2*dep[tf]);
    }

    for(auto e: g[u]){
        if(e.v==fa||e.v==son[tf]) continue;
        count(e.v,u,tf);
        if(u==tf) update(e.v,u,1);
    }
}

void dfs(int u,int fa,int op){
    for(auto e : g[u]){
        if(e.v==son[u]||e.v==fa) continue;
        dfs(e.v,u,0);
        dp[u] = max(dp[u],dp[e.v]); //先树形dp一波,最长链可能在子树当中
    }
    if(son[u]) dfs(son[u],u,1),dp[u]=max(dp[u],dp[son[u]]); 
    if(f[dis[u]]) dp[u]=max(dp[u],f[dis[u]]-dep[u]);
	for(int i=0;i<22;i++){
		if(f[dis[u]^(1<<i)]) dp[u]=max(dp[u],f[dis[u]^(1<<i)]-dep[u]);
    }
    f[dis[u]] = max(f[dis[u]],dep[u]);//更新最大深度
    //u不为链的一端
    count(u,fa,u);
    if(!op)  update(u,fa,0);
    //为什么可以直接清0,虽然我们f[]是多个状态的最大值,但是这些状态都被包括在子树中,所以大胆删就好
}

int main() {
    fst;
    int n,x; cin>>n;
    char c;
    for(int i=2;i<=n;i++){
        cin>>x>>c;
        g[i].push_back({x,c-'a'});
        g[x].push_back({i,c-'a'});
    }
    predfs(1,0);
    dfs(1,0,1);
    for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
    return 0;
}

标签:paths,Dokhtar,kosh,int,dfs,son,dep,dp,dis
From: https://www.cnblogs.com/touchfishman/p/17133795.html

相关文章