• [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]);
    }
    
  • 相关阅读:
    Java基础模拟LinkedList的双向链表结构
    对thrift版本降级(mac)
    通信技术类网站
    Arista和他的EOS
    程序员的英文能力培养
    挖掘时间的价值
    写作的方法论
    SRv6相关资源汇总
    你不必有自己的写作优势领域
    20192416汇编语言学习总结
  • 原文地址:https://www.cnblogs.com/zYzYzYzYz/p/14804325.html
Copyright © 2020-2023  润新知