树上DK路径 / Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
题目链接:ybt金牌导航6-2-4 / luogu CF741D
题目大意
给你一个 1 为根的树,边有字母编号。(字母只会是 a-v)
要你对于每个子树找一条最长的路径,使得这条路径上的每个字母编号按一定顺序排好之后是一个回文串。
思路
首先我们要看出回文串是什么鬼。
它说可以随便排顺序,那其实只要有两个相同的,那我们就可以把它放到对称要相等的位置。
那它就是要每个字母的出现顺序是奇数次的不能超过一次。(可以有一次时因为长度是奇数的话自己跟自己配对)
那这种奇偶的不难想到异或,那我们可以用异或来代替。
一看字母个数是
22
22
22 个,我们可以用二进制把它每个都表示出来,并存入数组。
再加上异或相等异或起来会抵消,我们完全可以直接
O
(
n
)
O(n)
O(n) 求每个点到根节点路径的二进制。
那由于每个子树都要求,你考虑先看看暴力怎么搞。
那你考虑有几种路径。
一个地方到根的路径。一个子树里面的路径。两个子树之间的路径。
那你暴力其实没差,你就枚举儿子,找到一个二进制跟前面的暴力配对,然后丢进数组里。
(数组记录的是这个二进制能有在树上最深的深度)
那你考虑优化,容易想到搞完一棵树要清空,那不难想到你可以选一个子树不清空。
那就是树上启发式合并,选重儿子。
至于匹配,你就看完全相同的(异或出来全是 0 0 0),以及枚举不用的哪一位,看有一位是不同的。
代码
#include<cstdio>
#include<bitset>
#include<vector>
using namespace std;
struct node {
int x, to, nxt;
}e[500001];
int n, fa, ans[500001];
int le[500001], KK, val[500001];
int sz[500001], son[500001], deg[500001];
int f[(1 << 22) + 1];
char c;
int max(int x, int y) {
return (x > y) ? x : y;
}
void add(int x, int y, int z) {
e[++KK] = (node){z, y, le[x]}; le[x] = KK;
}
void count(int now, int op) {//把数值丢进去统计(如果 op=0 就是清空数值)
if (op) f[val[now]] = max(f[val[now]], deg[now]);
else f[val[now]] = 0;
for (int i = le[now]; i; i = e[i].nxt)
count(e[i].to, op);
}
void dfs(int now) {//dfs 预处理
sz[now] = 1;
for (int i = le[now]; i; i = e[i].nxt) {
val[e[i].to] = val[now] ^ (1 << e[i].x);//出来这个点到根的异或和
deg[e[i].to] = deg[now] + 1;
dfs(e[i].to);
sz[now] += sz[e[i].to];
if (sz[e[i].to] > sz[son[now]]) {
son[now] = e[i].to;
}
}
}
int dfs1(int now) {//计算一个点两个子树之间的路径(以及轻儿子内部的路径)
int re = 0;
if (f[val[now]])
re = max(re, f[val[now]] + deg[now]);
for (int i = 0; i < 22; i++)
if (f[val[now] ^ (1 << i)])
re = max(re, f[val[now] ^ (1 << i)] + deg[now]);
for (int i = le[now]; i; i = e[i].nxt)
re = max(re, dfs1(e[i].to));
return re;
}
void work(int now) {
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != son[now]) {//先搞轻儿子
work(e[i].to);
ans[now] = max(ans[now], ans[e[i].to]);
count(e[i].to, 0);//要清空
}
if (son[now]) work(son[now]), ans[now] = max(ans[now], ans[son[now]]);//重儿子(不用清空)
if (f[val[now]]) ans[now] = max(ans[now], f[val[now]] - deg[now]);//它这个点匹配
for (int i = 0; i < 22; i++)
if (f[val[now] ^ (1 << i)])
ans[now] = max(ans[now], f[val[now] ^ (1 << i)] - deg[now]);
f[val[now]] = max(f[val[now]], deg[now]);//它单独
for (int i = le[now]; i; i = e[i].nxt)//它的儿子们匹配(由于重儿子内部已经匹配好了就不用再丢去匹配了)
if (e[i].to != son[now]) {
ans[now] = max(ans[now], dfs1(e[i].to) - 2 * deg[now]);
count(e[i].to, 1);
}
}
int main() {
scanf("%d", &n);
for (int i = 2; i <= n; i++) {
scanf("%d %c", &fa, &c);
add(fa, i, c - 'a');
}
deg[1] = 1;
dfs(1);
work(1);
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]);
return 0;
}