cfodeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

题目链接:Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

  第一次写\(dsu\ on\ tree\),来记录一下

  \(dsu\ on\ tree\)主要维护子树信息,往往可以省掉一个数据结构的启发式合并。大体思路如下:

  轻重链路径剖分之后,对每个点先递归处理他的所有轻儿子,每次处理完轻儿子之后把这棵子树的信息清空。最后再来处理重孩子,重儿子的信息就可以不用清空了。由于我们是用一个全局数组来记录信息的,重儿子子树的信息就仍然保留在全局数组中。接着我们另外写一个函数\(dfs\)所有的轻儿子子树,并统计答案。每统计完一棵子树的答案就可以把这棵子树的信息计入全局数组中,用于下一次更新。由于每个点到根的轻边条数是\(\log n\)级别的,所以每个点最多被扫\(\log n\)遍。

  回到这道题上来。由于要求路径上的所有字符重新排列之后可以形成一个回文串,也就是说出现次数为奇数的字符不会超过\(1\)个。那么我们就可以给每个字符一个\(2^x\)形式的权值,这样的话合法路径的异或和要么为\(0\),要么为\(2^x\)的形式。

  设点\(x\)到根的异或和为\(D_x\),由于这道题是边权,\(x\)和\(y\)路径上的异或和就可以用\(D_x\ xor\ D_y\)来表示。这样的话就可以用一个数组\(c\)来统计答案,其中\(c_i\)表示满足\(D_x=i\)的\(x\)的最大深度。剩下的就是套\(dsu\ on\ tree\)的板子了。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 500010
#define INF (1<<30)

using namespace std;
typedef long long llg;

int n,ci[1<<22],son[maxn],siz[maxn],dep[maxn];
int hd[maxn],nt[maxn],D[maxn],ans[maxn];

int getint(){
	int w=0;bool q=0;
	char c=getchar();
	while((c>‘9‘||c<‘0‘)&&c!=‘-‘) c=getchar();
	if(c==‘-‘) c=getchar(),q=1;
	while(c>=‘0‘&&c<=‘9‘) w=w*10+c-‘0‘,c=getchar();
	return q?-w:w;
}

void dfs(int u){
	siz[u]=1;
	for(int i=hd[u];i;i=nt[i]){
		D[i]^=D[u]; dep[i]=dep[u]+1;
		dfs(i); siz[u]+=siz[i];
		if(siz[i]>siz[son[u]]) son[u]=i;
	}
}

void undo(int u){
	ci[D[u]]=-INF;
	for(int i=hd[u];i;i=nt[i]) undo(i);
}

int o;
void up(int &x,int y){if(y>x) x=y;}
void up(int u){
	up(ans[o],dep[u]+ci[D[u]]);
	for(int i=0;i<=21;i++) up(ans[o],dep[u]+ci[1<<i^D[u]]);
	for(int i=hd[u];i;i=nt[i]) up(i);
}

void ins(int u){
	up(ci[D[u]],dep[u]);
	for(int i=hd[u];i;i=nt[i]) ins(i);
}

void work(int u){
	for(int i=hd[u];i;i=nt[i])
		if(i!=son[u]) work(i),undo(i);
	if(son[u]) work(son[u]); o=u;
	for(int i=hd[u];i;i=nt[i])
		if(i!=son[u]) up(i),ins(i);
	up(ci[D[u]],dep[u]);
	up(ans[u],dep[u]+ci[D[u]]);
	for(int i=0;i<=21;i++) up(ans[u],dep[u]+ci[1<<i^D[u]]);
	ans[u]-=dep[u]<<1;
	for(int i=hd[u];i;i=nt[i]) up(ans[u],ans[i]);
}

int main(){
	File("a");
	n=getint();
	for(int i=0;i<(1<<22);i++) ci[i]=-INF;
	for(int i=2,x;i<=n;i++){
		x=getint();
		nt[i]=hd[x];hd[x]=i;
		char c=getchar();
		while(c>‘v‘ || c<‘a‘) c=getchar();
		D[i]=1<<(c-‘a‘);
	}
	dfs(1); work(1);
	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}
时间: 2024-10-24 13:11:36

cfodeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths的相关文章

Codeforces 741 D - Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

D - Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 思路: 树上启发式合并 从根节点出发到每个位置的每个字符的奇偶性记为每个位置的状态,每次统计一下每个状态的最大深度 为了保证链经过当前节点u,我们先计算每个子树的答案,再更新子树状态对深度的贡献. 代码: #pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<bit

Codeforces 741D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

Description 以\(1\) 为根 的 \(n\) 个节点的树,每条边有一个颜色 \(x\),求每一个点的子树内的好的路径的最长长度 一条路径被定义为好的当且仅当把所有经过的边的字母经过排列之后可以变成回文 题面 Solution 理解了一下 \(dsu\,on\,tree\),相比普通的启发式,省去了高级的数据结构,并省下了大量空间 好的路径实际上就是出现奇数次的字母不多于一个,字符集只有 \(22\),可以状压起来 对于一条路径的异或和实际上可以看成 \(dis[x]\)^\(dis

Codeforces.741D.Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree 思路)

题目链接 \(Description\) 给定一棵树,每条边上有一个字符(a~v).对每个节点,求它的子树中一条最长的路径,满足 路径上所有边上的字符可以重新排列成一个回文串.输出其最长长度. \(n\leq 5\times10^5\). \(Solution\) 可以构成回文串,即要么所有字符都出现了偶数次,要么有一个出现了奇数次.其余都出现了偶数次. 转化为异或!把每个字符c(0~21)映射到1<<c上去. 令\(s[x]\)表示根节点到\(x\)路径上边权的异或和.那么路径\((u,v)

CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

重新排列后组成回文串意味着路径上出现奇数次的最多1个,那么可以\(dsu\ on\ tree\)搞一下了... /* mail: [email protected] author: MLEAutoMaton This Code is made by MLEAutoMaton */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> #include<algor

「CF741D」Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

传送门 Luogu 解题思路 考虑把22个字符状压下来,易知合法情况就是状态中之多有一个1,这个可以暴力一点判断23次. 然后后就是 dsu on the tree 了. 细节注意事项 咕咕咕 参考代码 #include <algorithm> #include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <cctype> #i

【学习笔记】dsu on tree

我也不知道为啥这要起这名,完完全全没看到并查集的影子啊-- 实际上原理就是一个树上的启发式合并. 特点是可以在$O(nlogn)$的时间复杂度内完成对无修改的子树的统计,复杂度优于莫队算法. 局限性也很明显:1.不能支持修改  2.只能支持子树统计,不能链上统计.(链上统计你不能直接树剖吗?) 那么它是怎么实现的呢?首先有一个例子:树上每个节点都有一个颜色(那么一定是蓝色), 求每个节点的子树上有多少颜色为k的节点.(每个节点的k不一定相同) $O(n^2)$的算法非常好想,以每个点为起点dfs

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

这个故事告诉我们,在做一个辣鸡出题人的比赛之前,最好先看看他发明了什么新姿势= =居然直接出了道裸题 参考链接: http://codeforces.com/blog/entry/44351(原文) http://blog.csdn.net/QAQ__QAQ/article/details/53455462 这种技巧可以在O(nlogn)的时间内解决绝大多数的无修改子树询问问题. 例1 子树颜色统计 有一棵n个点的有根树,根为1,每个点有一个1~n的颜色,对于每一个点给了一个数k,要询问这个子树

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

简介 对于一颗静态树,O(nlogn)时间内处理子树的统计问题.是一种优雅的暴力. 算法思想 很显然,朴素做法下,对于每颗子树对其进行统计的时间复杂度是平方级别的.考虑对树进行一个重链剖分.虽然都基于重链剖分,但不同于树剖,我们维护的不是树链. 对于每个节点,我们先处理其轻儿子所在子树,轻子树在处理完后消除其影响.然后处理重儿子所在子树,保留其贡献.然后再暴力跑该点的轻子树,统计该点子树的最终答案.如果该点子树是轻子树,则消除该子树的影响,否则保留.用代码描述的话,大概是这个流程: void d

dsu on tree:关于一类无修改询问子树可合并问题

dsu on tree:关于一类无修改询问子树可合并问题 开始学长讲课的时候听懂了但是后来忘掉了....最近又重新学了一遍 所谓\(dsu\ on\ tree\)就是处理本文标题:无修改询问子树可合并问题. \(dsu\)是并查集,\(dsu\ on\ tree\)是树上启发式合并,基于树剖(轻重链剖分). 无修改好理解,询问子树也好理解,啥是可合并啊? 举个简单的例子,集合的\(gcd\)就是可以合并的,就是两个集合\(gcd\)的\(gcd\):桶也是能合并的,对应位置相加就好了,诸如此类.