[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 类的结果乘起来,就是:
对,这是一组 \((x,y)\) 的结果,但是光这个复杂度好像就很高了,而且还有除法。别急,上面我说过这些多项式是特殊的。
首先上面的分子是可以预处理出来的,分 \(n\) 次相乘。这个相乘是有特殊做法的。
假设我们要算
前两个相乘的结果是 \(1+2x+2x^2+x^3\)。
你拿 \(1+x+x^2+x^3\) 和 \(1+2x+2x^2+x^3\) 相乘的结果是:
观察每一项的系数,能观察出来:\(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]);
}