CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(树上启发式合并)

题意

题面比题目还短

给一棵树,边上有一个字母(只有a到v共22个)。一条简单路径被称为Dokhtar-kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串。

求每个子树中最长的Dokhtar-kosh路径的长度。

鬼畜:
在这里插入图片描述

思路

一条路径符合要求,当他只有0或1种字母出现奇数次,所以用一个二进制数记录一条路径。

一条简单路径可以看成两条从根出发的链连在一起,表示为 x    x o r    y x\;xor\;y xxory,顺便还把lca到根的那一段重复给去掉了。

因为求得是每一棵子树的答案,套上树上启发式合并板子。对于每一棵子树,先更新答案再update,防止用两条同一子树的链更新答案。

萌新刚学树上Dsu,熟悉一下模板。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 5e5+10;
const int E = 1<<22;
const int B = 22;
int n, point[N];
struct EDGE{
	int nxt, v, w;
}edge[N<<1];
int val[N], dpt[N], sz[N], son[N], ans[N];
int dfn[N], rfn[N], ed[N], idx, len[E];

inline void chkMax(int &x, int y){if (x < y) x = y;}

inline void add_edge(int u, int v, int w, int e)
{
	edge[e] = (EDGE){point[u], v, w};
	point[u] = e;
}

void Dfs1(int u, int fa)
{
	dpt[u] = dpt[fa]+1;
	sz[u] = 1;
	son[u] = -1;
	for (int i = point[u]; i != -1; i = edge[i].nxt){
		int v = edge[i].v;
		int w = edge[i].w;
		if (v == fa) continue;
		val[v] = val[u]^(1<<w);
		Dfs1(v, u);
		sz[u] += sz[v];
		if (son[u] == -1 || sz[v] > sz[son[u]])
			son[u] = v;
	}
}

int Calc(int u)
{
	int ret = 0;
	if (len[val[u]])
		ret = dpt[u]+len[val[u]];
	for (int i = 0; i < B; ++ i)
		if (len[val[u]^(1<<i)])
			chkMax(ret, dpt[u]+len[val[u]^(1<<i)]);
	return ret;
}

void Add(int u)
{
	chkMax(len[val[u]], dpt[u]);
}

void Del(int u)
{
	len[val[u]] = 0;
}

void Dfs2(int u, int fa, int tp)
{
	int lght, delta = dpt[u]<<1;
	dfn[++ idx] = u;
	rfn[u] = idx;
	ans[u] = 0;
	
	for (int i = point[u]; i != -1; i = edge[i].nxt){
		int v = edge[i].v;
		if (v == fa || v == son[u]) continue;
		Dfs2(v, u, v);
		chkMax(ans[u], ans[v]);
	}
	lght = idx;
	
	if (son[u] != -1){
		Dfs2(son[u], u, tp);
		chkMax(ans[u], ans[son[u]]);
	}
	ed[u] = idx;
	
	for (int i = rfn[u]+1; i <= lght; i = ed[dfn[i]]+1){
		for (int j = i; j <= ed[dfn[i]]; ++ j) // 要非常清楚i,rfn[u],dfn[i]等的意义
			chkMax(ans[u], Calc(dfn[j])-delta);
		for (int j = i; j <= ed[dfn[i]]; ++ j)
			Add(dfn[j]);
	}
	chkMax(ans[u], Calc(u)-delta);
	Add(u);
	
	if (u == tp){
		for (int i = rfn[u]; i <= ed[u]; ++ i)
			Del(dfn[i]);
	}
}

int main()
{
	scanf("%d", &n);
	memset(point, -1, sizeof(point));
	for (int i = 2; i <= n; ++ i){
		int x; scanf("%d", &x);
		char c = getchar(); while (c < 'a' || c > 'v') c = getchar();
		add_edge(i, x, c-'a', (i-2)<<1);
		add_edge(x, i, c-'a', (i-2)<<1^1);
	}
	dpt[0] = -1; val[1] = 0;
	
	Dfs1(1, 0);
	
	memset(len, 0, sizeof(len));
	idx = 0;
	
	Dfs2(1, 0, 1);
	
	for (int i = 1; i <= n; ++ i)
		printf("%d ", ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值