[USACO19DEC]Tree Depth P 题解

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

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

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

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

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

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

证明1:

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

证明2:

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

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

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

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

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

把这 3 类的结果乘起来,就是:(prodlimits_{i=1}^{n}sumlimits_{j=0}^{i-1}t^i)

但是啊,你放的这个 (y) 造成的逆序对个数已经大于等于 (y-x) 了,上面的式子是错的,所以你需要将上述式子除以 (sumlimits_{i=0}^{y-x}t_i) ,结果是:

[frac{prodlimits_{i=1}^{n}sumlimits_{j=0}^{i-1}t^i}{sumlimits_{i=0}^{y-x}t_i} ]

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

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

假设我们要算

[(1+x) imes(1+x+x^2) imes(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=sumlimits_{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=sumlimits_{j=i-3}^j b_j) 这里的 3 就是 (1+x+x^2+x^3) 的最高次项指数。

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

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

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

根据乘法我们知道,(a_i=sumlimits_{j=i-2}^j b_j) ,那么我们现在需要做的操作就是将 (a_i) 减去 (a_i=sumlimits_{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) imes(1+x+x^2+x^3)) 的结果一样。

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

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

这样每次乘除的复杂度是 ( ext{O}(n^2)) 的,枚举 (x,y) 后,总的复杂度是 ( ext{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{prodlimits_{i=1}^{n}sumlimits_{j=0}^{i-1}t^i}{sumlimits_{i=0}^{y-x}t_i}) ,分母只有 (n-1) 种取值(只和 (y-x) 有关),所以我们可以枚举 (y-x),对于多组 ((x,y)) 同时求解答案。这样我们只需要 (n) 次乘除法,复杂度为 ( ext{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}) 中的最小值((xle 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," 
"[i==n]);
}
原文地址:https://www.cnblogs.com/zYzYzYzYz/p/14804325.html