[USACO19DEC]Tree Depth P 题解

一个点的深度等于树中它祖先的个数(包括自己)。

那么我们可以对于一个点对 \((x,y)\) 考虑在所有排列中 \(y\) 做了几次 \(x\) 的祖先。

在笛卡尔树上如果 \(y\)\(x\) 的祖先那么 \(a_y\)\(a_{x..y}\) 中的最小值。(这里先设 \(x\le y\)

那么我们可以求:有多少个逆序对个数为 \(K\) 的排列,满足 \(a_y\)\(a_{x..y}\) 中的最小值(\(x\le y\))。

前置1:有多少个逆序对个数为 \(K\)\(n\) 元排列。

先放结论:答案是 \(\prod\limits_{i=1}^{n}\sum\limits_{j=0}^{i-1}t^i\)\(t^K\) 次项系数,这里用到了生成函数知识。

证明1:

  • 我们考虑相对大小。从左到右安排每一个位置,在考虑第 \(i\) 个数时,它前面有 \(i-1\) 个数,它可以成为第 \(1\text{~}i\) 小的任意一个数,增加的逆序对个数为 \(0\text{~}i-1\)。最小时新增逆序对为 \(i-1\) ;第 \(i\) 小时,它就是最大的数,新增的逆序对数为 0。
  • 从右到左安排也是可以的,只不过考虑它可以成为第 \(1\text{~}i-1\) 的数字中的任意一个,逆序对增加的量也是 \(0\text{~}i-1\) 。所以我们可以往前安排也可以往后安排,就是说想放哪放哪。记住这一段话。

证明2:

  • 我们考虑相对位置。从小到大安排每一个数字,在考虑数字 \(i\) 时,比它小的数排成一排,它可以插入在第 \(1\text{~}i\) 中任意一个位置中,插入到最前面时,新增的逆序对个数为 \(i-1\);插入到最后面时,新增的逆序对个数为 0。
  • 其实这个证明不看也是可以的,对这个题好像没什么帮助,但证明方法越多越好,说不定以后什么题就需要这个证明2。

所以不管怎么放数字,放第 \(i\) 个时,逆序对新增的数目可以为 \(0\text{~}i-1\) 中的任意一个,把它当成一个多项式。全部乘起来取第 \(K\) 项系数就行了。(但是直接乘是会 TLE 的,由于这些多项式是特殊的,指数是连续的,有更高妙的方法将他们乘起来,这个放到后面讲。)。

然后返回原始的问题:有多少个逆序对个数为 \(K\) 的排列,满足 \(a_y\)\(a_{x..y}\) 中的最小值(\(x\le y\))。

将排列按照下标分成 \([1,x-1]\cup[x,y-1]\cup\{y\}\cup[y+1,n]\) 四部分,分成三类处理:

  • 先考虑 \([x,y-1]\) 这一块。显然,逆序对为 \(K\) 的排列有 \(\prod\limits_{i=1}^{y-x}\sum\limits_{j=0}^{i-1}t^i\)\(K\) 次项系数个。
  • 再考虑 \(\{y\}\) ,由于我们假设 \(a_y\)\(a_{x..y}\) 中的最小值(\(x\le y\)),那么逆序对个数加 \(y-x\) 个。
  • 最后考虑 \([1,x-1]\)\([y+1,n]\) 这两块,用证明1的那个构造方法,我们要将第 \(y-x+2\) 个到第 \(n\) 个数字安排好。让你记住的那句话告诉我们,我们可以先放完 \([1,x-1]\),再放完 \([y+1,n]\);甚至我们可以 xjb 放,左边一个右边一个轮流来都可以。但是放的第 j 个数时可以新增的逆序对的范围始终是一样的,生成函数乘起来是 \(\prod\limits_{i=y-x+2}^n\sum\limits_{j=0}^{i-1} t^i\)

把这 3 类的结果乘起来,就是:

\[\frac{\prod\limits_{i=1}^{n}\sum\limits_{j=0}^{i-1}t^i}{\sum\limits_{i=0}^{y-x}t^i} \]

对,这是一组 \((x,y)\) 的结果,但是光这个复杂度好像就很高了,而且还有除法。别急,上面我说过这些多项式是特殊的。

首先上面的分子是可以预处理出来的,分 \(n\) 次相乘。这个相乘是有特殊做法的。

假设我们要算

\[(1+x)\times(1+x+x^2)\times(1+x+x^2+x^3) \]

前两个相乘的结果是 \(1+2x+2x^2+x^3\)

你拿 \(1+x+x^2+x^3\)\(1+2x+2x^2+x^3\) 相乘的结果是:

\[1+3x+5x^2+6x^3+5x^4+3x^5+x^6 \]

观察每一项的系数,能观察出来:\(a_i=\sum\limits_{j=i-3}^j b_j\),这里的 \(a_i\)\(1+3x+5x^2+6x^3+5x^4+3x^5+x^6\)\(i\) 次项系数,\(b_j\)\(1+2x+2x^2+x^3\)\(j\) 次项系数。意思是,新的 \(a_i\) 可以从之前的 \(b_j\) 的一部分转移过来,这一部分的大小和 \(1+x+x^2+x^3\) 的最高次幂有关,因为它的指数又是连续的,所以 \(a_i\) 可以从 \(b_j\) 连续的一部分转移过来。\(a_i=\sum\limits_{j=i-3}^j b_j\) 这里的 3 就是 \(1+x+x^2+x^3\) 的最高次项指数。

所以乘法就转换成了区间求和,可以差分一下,再求个前缀和。

对于除法就相当于是乘法的逆过程。

假如我们有了 \((1+x)\times(1+x+x^2)\times(1+x+x^2+x^3)=1+3x+5x^2+6x^3+5x^4+3x^5+x^6\) ,我们需要将它除以 \(1+x+x^2\)

根据乘法我们知道,\(a_i=\sum\limits_{j=i-2}^j b_j\) ,那么我们现在需要做的操作就是将 \(a_i\) 减去 \(a_i=\sum\limits_{j=i-2}^{j-1} b_j\)

\(i=0\) 减到 \(i=6\) 得:

\(\{1,3,5,6,5,3,1\},i=0\)

\(\{1,2,5,6,5,3,1\},i=1\)

\(\{1,2,2,6,5,3,1\},i=2\)

\(\{1,2,2,2,5,3,1\},i=3\)

\(\{1,2,2,2,1,3,1\},i=4\)

\(\{1,2,2,2,1,0,1\},i=5\)

\(\{1,2,2,2,1,0,0\},i=6\)

即:\(1+2x+2x^2+2x^3+x^4\),和 \((1+x)\times(1+x+x^2+x^3)\) 的结果一样。

同样可以通过差分和前缀和处理。

注意:加法从后往前倒着处理(类似于01背包),除法从前往后处理。

这样每次乘除的复杂度是 \(\text{O}(n^2)\) 的,枚举 \(x,y\) 后,总的复杂度是 \(\text{O}(n^4)\)

代码片段:

for(int i=1;i<n;i++)mul(i);//分子
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++){//枚举 x,y
	div(i-j);//除法
	if(K>=(i-j))(ans[j]+=f[K-(i-j)])%=mod;
        //这个意思是,因为你 a_i 是最大的,肯定会有 (i-j) 个逆序对,我们需要从其他地方找到 K-(i-j) 个逆序对。
	mul(i-j);//乘法
}

足以 TLE 此题。所以我们需要优化这个。

注意到上面这个式子 \(\frac{\prod\limits_{i=1}^{n}\sum\limits_{j=0}^{i-1}t^i}{\sum\limits_{i=0}^{y-x}t_i}\) ,分母只有 \(n-1\) 种取值(只和 \(y-x\) 有关),所以我们可以枚举 \(y-x\),对于多组 \((x,y)\) 同时求解答案。这样我们只需要 \(n\) 次乘除法,复杂度为 \(\text{O}(n^3)\) 足以通过此题。

代码片段:

for(int i=1;i<n;i++){
	div(i);
	if(K>=i){
		for(int j=1;j<=n-i;j++)
		(ans[j]+=f[K-i])%=mod;
	}
	mul(i);
}

但是,这样做真的完了吗?

好像还没有,观察观察这句话:

那么我们可以求:有多少个逆序对个数为 \(K\) 的排列,满足 \(a_y\)\(a_{x..y}\) 中的最小值(\(x\le y\))。

如果 \(x>y\) 呢?

那么排列就是 \(a_y,\dots,a_x\),这时,你统计的是 \(x\) 的祖先的个数,而放入 \(a_y\) 不会增加逆序对。

我们需要对上面的代码做一些小改动:

for(int i=1;i<=n;i++)
for(int j=1;j<i;j++){
	div(i-j);
	if(K>=(i-j))(ans[j]+=f[K-(i-j)])%=mod;
	(ans[i]+=f[K])%=mod;//由于 a_j 是最小的 没有新增的逆序对,直接用 f[K],加到i上是因为,这时候j是i的祖先。
	mul(i-j);
}
for(int i=1;i<n;i++){
	div(i);
	if(K>=i){
		for(int j=1;j<=n-i;j++)
		(ans[j]+=f[K-i])%=mod;
	}
	for(int j=1+i;j<=n;j++)//这里改动的原因和上面一模一样
		(ans[j]+=f[K])%=mod;
	mul(i);
}

但是,这样做真的完了吗?

好像还没有,观察观察这句话:

一个点的深度等于树中它祖先的个数(包括自己)。

自己呢???好像咱们没有统计,对于每个位置直接加上 \(f[K]\) 就行。

最终代码:

#include<bits/stdc++.h>
using namespace std;
int n,K,top,f[45000],mod,ans[310];
inline int read()
{
	int x=0,w=0;char ch=0;
	while(!isdigit(ch)){w|=ch=='0';ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return w?-x:x;
}
void mul(int x)
{
	for(int i=top;i>=0;i--)
		(f[i+x+1]-=f[i])%=mod;
	top+=x;
	for(int i=1;i<=top+1;i++)
		(f[i]+=f[i-1])%=mod;
}
void div(int x)
{
	for(int i=top;i;i--)
		(f[i]-=f[i-1])%=mod;
	top-=x;
	for(int i=0;i<=top;i++)
		(f[i+x+1]+=f[i])%=mod;
}
int main()
{
	f[0]=1;
	n=read();K=read();mod=read();
	for(int i=1;i<n;i++)mul(i);
	for(int i=1;i<n;i++){
		div(i);
		if(K>=i){
			for(int j=1;j<=n-i;j++)
			(ans[j]+=f[K-i])%=mod;
		}
		for(int j=1+i;j<=n;j++)
			(ans[j]+=f[K])%=mod;
		mul(i);
	}
	for(int i=1;i<=n;i++)
		printf("%d%c",((ans[i]+f[K])%mod+mod)%mod," \n"[i==n]);
}
posted @ 2021-05-24 14:48  zYzYzYzYz  阅读(122)  评论(0编辑  收藏  举报