题目描述

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

输入输出样例

输入 #1

4

1 s

2 a

3 s

输出 #1

3 1 1 0

输入 #2

5

1 a

2 h

1 a

4 h

输出 #2

4 1 0 1 0

分析

一道树上启发式合并的好题

首先,我们来考虑什么样的情况下路径上的字符重新排列之后能够形成回文串

很显然,只有当路径上每种字母的数量都为偶数个或者有且仅有一种字母的数量是奇数个时才满足条件

这两种情况分别对应奇回文串和偶回文串

然后我们会发现字母只有 \(22\) 种,因此字母的状态可以状压

而题目的要求仅仅是判断奇偶性,因此我们用 \(0\) 表示偶数,用 \(1\) 表示奇数

那么满足要求的状态只有 \(0\) 和 \(2^i\)

那么我们就可以存储每一种状态所对应的节点的最大深度

转移时,当前的结点的 \(dp\) 值会由三种情况转移过来

1、在儿子节点的 \(dp\) 值中取 \(max\)

2、从儿子节点中选择一条链和当前的节点组成一条新的链

3、从两个不同的儿子节点中选择两条链和当前的节点组成一条新的链

为了避免出现自己更新自己的情况,我们要计算完一个儿子节点后再计算另一个儿子节点

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
const int maxn=4e6+5;
int h[maxn],tot=1,n;
struct asd{
int to,nxt,val;
}b[maxn];
void ad(int aa,int bb,int cc){
b[tot].to=bb;
b[tot].nxt=h[aa];
b[tot].val=cc;
h[aa]=tot++;
}
int siz[maxn],son[maxn],dep[maxn];
int f[maxn],dp[maxn],orz,ans[maxn<<2],yh[maxn],mmax,haha;
void dfs1(int now,int fa){
siz[now]=1;
dep[now]=dep[fa]+1;
f[now]=fa;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==fa) continue;
yh[u]=yh[now]^(1<<b[i].val);
dfs1(u,now);
siz[now]+=siz[u];
if(son[now]==0 || siz[u]>siz[son[now]]){
son[now]=u;
}
}
}
void js(int now){
if(ans[yh[now]]) mmax=std::max(mmax,ans[yh[now]]+dep[now]-haha);
for(int i=0;i<=22;i++){
if(ans[yh[now]^(1<<i)])mmax=std::max(mmax,ans[yh[now]^(1<<i)]+dep[now]-haha);
//只有当ans值存在的时候才能转移
}
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==f[now] || u==orz) continue;
js(u);
}
}
//计算子树对父亲节点的贡献
void add(int now,int op){
if(op==1){
ans[yh[now]]=std::max(ans[yh[now]],dep[now]);
} else {
ans[yh[now]]=0;
}
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==f[now] || u==orz) continue;
add(u,op);
}
}
//加入或删除子树贡献
void dfs2(int now,int fa,int op){
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==f[now] || u==son[now]) continue;
dfs2(u,now,0);
dp[now]=std::max(dp[now],dp[u]);
}
if(son[now]){
dfs2(son[now],now,1);
orz=son[now];
dp[now]=std::max(dp[now],dp[son[now]]);
}
//先递归轻儿子,再递归重儿子
haha=dep[now]*2;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==orz || u==fa) continue;
js(u);
add(u,1);
}
//计算完一个子树的贡献再加入另一个子树的贡献
mmax=std::max(mmax,ans[yh[now]]-dep[now]);
for(rg int i=0;i<=22;i++){
mmax=std::max(mmax,ans[yh[now]^(1<<i)]-dep[now]);
}
//即使ans值不存在,也不会更新
ans[yh[now]]=std::max(ans[yh[now]],dep[now]);
//从子树中选择一条链和当前节点连起来
orz=0;
dp[now]=std::max(mmax,dp[now]);
if(op==0){
add(now,-1);
//清除轻儿子贡献
mmax=haha=0;
}
}
int main(){
memset(h,-1,sizeof(h));
scanf("%d",&n);
rg int aa;
char bb;
for(rg int i=2;i<=n;i++){
scanf("%d %c",&aa,&bb);
ad(aa,i,bb-'a');
ad(i,aa,bb-'a');
}
dfs1(1,0);
dfs2(1,0,0);
for(int i=1;i<=n;i++){
printf("%d ",dp[i]);
}
printf("\n");
return 0;
}

CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 树上启发式合并(DSU ON TREE)的更多相关文章

  1. 神奇的树上启发式合并 (dsu on tree)

    参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...

  2. 树上启发式合并 (dsu on tree)

    这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题 参考链接: http://codeforces.com/blog/entry/44351(原文) ...

  3. 树上启发式合并(dsu on tree)学习笔记

    有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...

  4. dsu on tree (树上启发式合并) 详解

    一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...

  5. dsu on tree 树上启发式合并 学习笔记

    近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...

  6. CF600E Lomsat gelral——线段树合并/dsu on tree

    题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...

  7. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  8. codeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(启发式合并)

    codeforces 741D Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths 题意 给出一棵树,每条边上有一个字符,字符集大小只 ...

  9. dsu on tree[树上启发式合并学习笔记]

    dsu on tree 本质上是一个 启发式合并 复杂度 \(O(n\log n)\) 不支持修改 只能支持子树统计 不能支持链上统计- 先跑一遍树剖的dfs1 搞出来轻重儿子- 求每个节点的子树上有 ...

随机推荐

  1. SpringMVC实例及注解(二)

    @RequestMapping()除了修饰方法,还可以修饰类1.类定义处:提供初步的请求映射信息.相对于WEB应用的根目录2.方法处:提供进一步的细分映射信息.相对于类定义处的URL.若类定义处未标注 ...

  2. CentOS 7常用命令

    常用命令 文件与目录操作 命令 解析 cd /home 进入 ‘/home’ 目录 cd .. 返回上一级目录 cd ../.. 返回上两级目录 cd - 返回上次所在目录 cp file1 file ...

  3. leetcode刷题-53最大子序和

    题目 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 思路 动态规划:求整个数组的连续子数组的最大和,可以求出每个位置的连续子数组的最大和,返回 ...

  4. if __name__ == ‘__main__‘

    if __name__ == '__main__': def_test() 作为程序的入口,当函数被调用时会从此处开始运行 如被导入的模块内没写 if __name__ == '__main__',则 ...

  5. 一个SQL查询连续三天的流量100以上的数据值【SQql Server】

    题目 有一个商场,每日人流量信息被记录在这三列信息中:序号 (id).日期 (date). 人流量 (people).请编写一个查询语句,找出高峰期时段,要求连续三天及以上,并且每天人流量均不少于10 ...

  6. linux下禁止root和匿名用户登录ftp

    1.ftp通过root或其他用户进入可视化界面权限过大,容易导致系统文件误删 windows下输入ftp://IP去访问,不需要账号密码就可以访问到ftp文件夹 刚进去pub是空的,在linux上新增 ...

  7. loadrunner跑场景时报错Full MDB file. New error messages will be ignored

    这个原因是在controller跑场景时,controller的日志文件占用内存太大 解决办法:先找到controller的日志文件Results——Results Setting——找到日志的路径, ...

  8. 关于JSON的零碎小知识

    1.ali的fastjson在将实体类转成jsonString的时候,一些首字母大写的字段会自动修改为小字母,这种字段加 @JsonProperty(value = "DL_id" ...

  9. .NET 5 中 Target Framework 详解

    作者:.NET Team 翻译:精致码农-王亮 原文:http://dwz.win/Q4v 我们希望极大地简化开发人员必须在项目文件和 NuGet 包中使用的TFM (Target Framework ...

  10. Linux系统编程—信号量

    大家知道,互斥锁可以用于线程间同步,但是,每次只能有一个线程抢到互斥锁,这样限制了程序的并发行.如果我们希望允许多个线程同时访问同一个资源,那么使用互斥锁是没有办法实现的,只能互斥锁会将整个共享资源锁 ...