【NOI2013】树的计数

简述

给定树的\(\texttt{DFS}\)序和\(\texttt{BFS}\)序,求同时满足两者的所有树的树高平均值,保证至少有一个解。(这里树高是最大深度加1,根节点深度为0)

题解

首先,比较容易看出来的性质是\(\texttt{BFS}\)序的不同分段可以确定不同的深度方案,每一段内的节点深度相同且段与段之间深度满足深度递增和相差1的条件。较为容易想到的是,这道题该转化为利用树的性质在区间上的\(\texttt{dp}\)。方便起见,我们依次将\(\texttt{BFS}\)序中的节点重新编号为\(1\sim n\),设\(pos_i\)表示\(i\)\(\texttt{DFS}\)序中的位置,记\(\texttt{DFS}\)序为\(\{a_1,a_2,\cdots, a_n\}\)

性质1

满足题目所求条件的两棵不同的树其\(\texttt{BFS}\)序的分段一定不同。

证明

假设两者分段相同,由于两者为不同的树,则一定存在一个节点\(x\)在一棵树中父亲为\(u\),另一棵树中父亲为\(v\)\(u,v\)处于\(\texttt{BFS}\)序的同一段中。
不妨设\(u<v\),则在第一棵树的\(\texttt{DFS}\)序中\(u\)出现的顺序在\(x\)之前,\(v\)出现在\(x\)之后;而在第二棵树的\(\texttt{DFS}\)序中\(u\)出现的顺序在\(v\)之前,\(x\)出现在\(v\)之后。无法构成一样的\(\texttt{DFS}\)序。

于是此题便转化成了统计\(\texttt{BFS}\)序不同分段方式的问题。接下来要考虑的是如何转化限制条件将每种方案不重不漏地找出。

性质2

根节点1单独成一段,其余的任意一段\(l\sim r\)满足\(pos_l<pos_{l+1}\cdots <pos_r\)

证明

根节点自成一段自然不用赘述原因。由于\(\texttt{BFS}\)序中深度相同的点的遍历到的先后顺序应该和\(\texttt{DFS}\)序遍历到的先后顺序相同(想想自己平时是怎么遍历树的),所以理所应当的\(pos_l,pos_{l+1},\cdots ,pos_r\)\(\texttt{DFS}\)序的子序列。

性质3

\(dep_i\)表示节点\(i\)的深度,则\(dep_{a_{i+1}}\le dep_{a_i}+1\)

证明

\(\texttt{DFS}\)序中\(a_{i+1}\)要么为\(a_{i}\)的儿子,要么是遍历完\(a_i\)回溯得到的节点(\(a_i\)是叶子的情况下),则得到\(dep_{a_{i+1}}\le dep_{a_i}+1\)

结论

所有满足以上三个性质的\(\texttt{BFS}\)序的分段方案对应所有合法的构造树的方案。其必要性已在性质中证明,下证充分性。

证明

证明充分性只需证明由以上三个限制条件可以唯一地构造出一棵树。每一层有什么点已经由\(\texttt{BFS}\)序的分段方式得出,接下来看如何通过\(\texttt{DFS}\)序确定连边。
假设当前构造到某一段\(l\sim r\)。则对于\(\forall i \in [l,r]\)\(a_{pos_i}\sim a_{pos_{i+1}-1}\)\(i\)的子树的\(\texttt{DFS}\)序,令\(a_{pos_i}\sim a_{pos_{i+1}-1}\)中属于下一段的节点就是点\(i\)的儿子。
照这个构造规则递归构造即可唯一地确定一棵树,可以自己拿样例手玩一下。

好了,接下来要解决的问题就是如何将以上三个条件解释为区间上的形式。这里引入一个01序列\({x_n}\),若对于\(i\in[1, n-1]\)\(x_i=1\),说明其与\(i+1\)不在同一段中。树高即为\(1+\sum_{i=1}^{n-1} x_i\)
由性质2可得,\(x_1=1\),若\(pos_i>pos_{i+1}\)\(x_i=1\)(分开以保证每一段中\(pos\)递增的性质)。
由性质3可得,若\(a_i<a_{i+1}\),则因为在\(\texttt{BFS}\)序中节点深度单调不降,\(dep_{a_i}\le dep_{a_{i+1}}\le dep_{a_i}+1\)。这就要求\(a_i\)\(a_{i+1}\)最多相差一段,即\(x_{a_i},x_{a_{i} +1},\cdots, x_{a_{i+1}}\)中最多有一个1,\(\sum_{j=a_i}^{a_{i+1}-1} x_j\le 1\)。若\(a_i>a_{i+1}\)\(dep_{a_i}\ge dep_{a_{i+1}}\),自然满足\(dep_{a_{i+1}}\le dep_{a_i}+1\),不用管。
用这两种约束直接做可以达到\(O(n^2)\)的复杂度获得85分,想要通过这道题还需要挖掘更深的性质。

考虑两种约束之间的关系:若某个限制为1的单点存在于子段和不得超过1的区间中,那么这个区间的约束就消失了,因为没有在别的地方放1或者全部不放1的可能。接下来找一找什么样的区间约束不受单点约束影响,就是\(a_i<a_{i+1}\)\(pos_{a_i}<pos_{a_{i}+1}<\cdots<pos_{a_{i+1}}\)。由于\(pos_{a_i}=i,pos_{a_{i+1}}=i+1\),所以\(pos_{a_i}\)\(pos_{a_{i+1}}\)之间不能有别的数出现,也就是\(a_{i+1}=a_i+1\),这些约束实际上没有效果(就一个位置,想填什么都可以)。那么实际上刨去没有效果的区间约束后,每个区间约束中除了被限制为1的点都是强制为0,其它不在区间约束中的填0或1都可以。又因对于每个不受限制的位置来说,其填0的方案数和填1的方案数都为总方案数的1/2,所以它对答案的贡献为0.5;受限制必须为1的在每个方案中都出现,贡献为1。
如果不是强制为1的话,只需要利用差分数组判断该位置是否处在有效区间约束中来确定其贡献为0还是0.5。

代码

#include <cstdio>
#include <cctype>
#include <cmath>
const int maxn=2e5+10;
int a[maxn],d[maxn],id[maxn],pos[maxn];

int read()
{
	int res=0;
	char ch=getchar();
	while(!isdigit(ch))
		ch=getchar();
	while(isdigit(ch))
		res=res*10+ch-'0',ch=getchar();
	return res;
}
int main()
{
	int n=read();
	for (int i=1;i<=n;i++)
		a[i]=read();
	for (int i=1;i<=n;i++)
		id[read()]=i;
	for (int i=1;i<=n;i++)
		a[i]=id[a[i]];
	for (int i=1;i<=n;i++)
		pos[a[i]]=i;
	for (int i=2;i<=n-1;i++)
		if (a[i]<a[i+1]-1)
			d[a[i]]++,d[a[i+1]]--;
	int s=0;
	double ans=2;
	for (int i=2;i<=n-1;i++)
	{
		s+=d[i];
		if (pos[i]>pos[i+1])
			ans+=1;
		else if (!s)
			ans+=0.5;
	}
	printf("%.3lf\n",ans);
	return 0;
}

参考

  • 《树的计数》解题报告 —— 南京外国语学校 乔明达(《2013全国信息学奥林匹克年鉴》)
posted @ 2020-07-29 17:08  hkr04  阅读(174)  评论(0编辑  收藏  举报