传送门
因为题目名字太长会触发博客标题的神奇BUG所以就省略了题目名字/cy
以前一直在胡dsu on tree但是从来没写过,这次还是写写比较好。
我们维护一个数组\(f_S\)表示子树的所有路径中出现次数为奇数的路径集合为\(S\)时最大的长度,每当增加一条路径\(T\)的时候,当\(popcount(S \bigoplus T) \leq 1\)的时候可以贡献答案,这样的\(S\)最多只有\(\sigma + 1\)个,所以可以暴力枚举。
所以我们只需要快速合并子树信息并计算子树答案。考虑dsu on tree。
按照dsu on tree的惯常策略,解决当前点的时候先将其轻儿子解决掉然后清空掉\(f\)数组,最后解决重儿子。注意到将重儿子的数组上传还需要加上一条边产生的字符的影响,并且数组中所有元素的值均\(+1\)。我们可以使用两个标记,一个表示下标异或、一个表示全局加法标记,就可以解决这个问题。
当然打标记就意味着在统计的过程中有一些细节需要注意。
#include
using namespace std;
const int _ = 5e5 + 7;
#define PII pair < int , int >
#define st first
#define nd second
vector < PII > ch[_]; int mx[1 << 22] , id[_] , ans[_] , sz[_] , son[_] , mrk1[_] , mrk2[_] , N;
void dfs1(int x){sz[x] = 1; for(auto t : ch[x]){dfs1(t.st); sz[x] += sz[t.st]; if(sz[son[x]] < sz[t.st]) son[x] = t.st;}}
void clr(int x , int v){mx[v] = -1e9; for(auto t : ch[x]) clr(t.st , v ^ t.nd);}
void add(int x , int v , int l){mx[v] = max(mx[v] , l); for(auto t : ch[x]) add(t.st , v ^ t.nd , l + 1);}
void dsu(int x , int v , int l , int &ans){
ans = max(ans , mx[v] + l); for(int i = 0 ; i < 22 ; ++i) ans = max(ans , mx[v ^ (1 << i)] + l);
for(auto t : ch[x]) dsu(t.st , v ^ t.nd , l + 1 , ans);
}
void dfs(int x){
if(son[x]){
for(auto t : ch[x]) if(t.st != son[x]){dfs(t.st); clr(t.st , mrk1[t.st]);} else mrk1[x] ^= t.nd;
dfs(son[x]); mrk1[x] ^= mrk1[son[x]]; mrk2[x] = mrk2[son[x]] + 1;
ans[x] = max(ans[x] , max(mx[mrk1[x]] + mrk2[x] , ans[son[x]]));
for(int i = 0 ; i < 22 ; ++i) ans[x] = max(ans[x] , mrk2[x] + mx[mrk1[x] ^ (1 << i)]);
mx[mrk1[x]] = max(mx[mrk1[x]] , -mrk2[x]);
for(auto t : ch[x])
if(t.st != son[x]){
dsu(t.st , t.nd ^ mrk1[x] , 1 + mrk2[x] , ans[x] = max(ans[x] , ans[t.st]));
add(t.st , t.nd ^ mrk1[x] , 1 - mrk2[x]);
}
}
else mx[0] = 0;
}
int main(){
ios::sync_with_stdio(0); cin >> N; memset(mx , -0x3f , sizeof(mx));
for(int i = 2 ; i <= N ; ++i){int fa; char s; cin >> fa >> s; ch[fa].push_back(PII(i , 1 << (s - 'a')));}
dfs1(1); dfs(1); for(int i = 1 ; i <= N ; ++i) printf("%d " , ans[i]);
return 0;
}