1. 程式人生 > 實用技巧 >LOJ3228 Tree Depth 和 LOJ6077 逆序對

LOJ3228 Tree Depth 和 LOJ6077 逆序對

Tree Depth

考慮所有長度為\(N\)有恰好\(K\)個逆序對的排列。

對於每個位置\(i\),求出其在所有滿足條件的排列中,在笛卡爾樹上的深度和。

對讀入的質數\(\text{MOD}\)取模。

\(N ≤ 300,K ≤\binom{N}{2}, \text{MOD} ∈ [10^8, 10^9 + 9]\)

題解

題目對排列的逆序對個數有要求,考慮能順便計算逆序對個數的構造排列的兩種過程:

  1. 按照權值從小到大構造。構造出\(1\sim x-1\)的相對位置後,決定插入\(x\)的相對位置。新增的逆序對個數\(\in [0,x-1]\)

  2. 按照位置從前往後構造。構造出\(P_1\sim P_{x-1}\)

    的相對大小後,決定賦值給\(P_x\)的相對大小。新增的逆序對個數\(\in [0,x-1]\)

回到這道題。題目要求笛卡爾樹上每個節點的深度和,利用期望的線性性轉化為\(j\)\(i\)的祖先的方案數。

考慮\(j\)\(i\)的祖先需要滿足的條件,\(\min_{\min\{i,j\}\leq k\leq \max\{i,j\}}\{P_k\}=P_j\)。顯然第二種構造方法更有利於這種形式的計算。

注意到第二種方法能執行的條件只需要當前填放的位置是連續的,所以我們可以改變一下填放順序。

考慮把插入的過程分成兩部分:

  1. \(i\)開始向\(j\)的方向,決定每個元素在當前排列中的相對大小。

  2. \(i\)開始向\(j\)的反方向,決定每個元素在當前排列中的相對大小。

這樣除了需要成為\(i\)祖先的\(j\)的區間縮成一個單點之外,每次新產生的逆序對數目區間一定是\([0,1] , [0,2] , ⋯ ,[0,N − 1]\)這樣。

  • \(j < i\)時,\(j\)必定產生\(0\)個逆序對。

  • \(j >i\)時,\(j\)必定產生\(j − i\)個逆序對。

先假裝下標為\(i\)的每次產生逆序對個數在\([0, i − 1]\)內,做一次\(DP\)。每次列舉\(|j-i|\),撤銷掉其本來的貢獻;然後列舉\(i\)計算其對\(i\)答案的貢獻。

注意到這個DP過程的本質是揹包,所以可以用多項式計算。用\(1+x+x^2+\dots+x^i=\frac{x^{i+1}-1}{x-1}\)

計算來保證複雜度。

時間複雜度\(O(N^3)\)

CO int N=301;
int ans[N];

int main(){
	int n=read<int>(),K=read<int>();
	read(mod);
	poly F={1};
	for(int d=1;d<n;++d){
		F.resize(F.size()+(d+1));
		for(int i=(int)F.size()-1;i>=0;--i)
			F[i]=add(i-(d+1)>=0?F[i-(d+1)]:0,mod-F[i]);
		for(int i=0;i<=(int)F.size()-1-1;++i)
			F[i]=add(mod-F[i],i-1>=0?F[i-1]:0);
		F.resize(F.size()-1);
	}
	fill(ans+1,ans+n+1,F[K]);
	for(int d=1;d<n;++d){
		poly G=F;G.resize(G.size()+1);
		for(int i=(int)G.size()-1;i>=0;--i)
			G[i]=add(i-1>=0?G[i-1]:0,mod-G[i]);
		for(int i=0;i<=(int)G.size()-1-(d+1);++i)
			G[i]=add(mod-G[i],i-(d+1)>=0?G[i-(d+1)]:0);
		G.resize(G.size()-(d+1));
		for(int i=1;i<=n;++i){
			if(i-d>=1)
				ans[i]=add(ans[i],0<=K and K<(int)G.size()?G[K]:0);
			if(i+d<=n) 
				ans[i]=add(ans[i],0<=K-d and K-d<(int)G.size()?G[K-d]:0);
		}
	}
	for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	return 0;
}

暴力實現多項式乘法的時候,意義十分明顯。

暴力實現多項式除法的時候,如果保證了能夠整除,那麼從低位到高位考慮意義比較好想。

逆序對

給定 \(n, k\),請求出長度為 \(n\) 的逆序對數恰好為 \(k\) 的排列的個數。答案對 \(10 ^ 9 + 7\) 取模。

對於 \(100\%\) 的資料,\(1 \leq n, k \leq 100000, 1 \leq k \leq \binom{n}{2}\)

題解

https://ouuan.github.io/post/loj6077-逆序對生成函式計數dp/

從小到大依次考慮將每個數插入排列,那麼每個數 \(i\) 都可以貢獻 \(0\dots i-1\) 個逆序對,所以答案的生成函式為 \((1 + x)(1 + x + x^2)\cdots(1+x+\cdots+x^{n-1})\)

上下同時乘上 \((1-x)^n\),即求:

\[\frac{(1-x)(1-x^2)\cdots(1-x^n)}{(1-x)^n} \]

(不約分是為了方便求。)

分母 \(\frac{1}{(1-x)^n}=\sum\limits_{i\ge 0}\binom{n-1+i}{n-1}x^i\),是一個大家熟知的結論,可以利用 \((1+x+x^2+\cdots)^n\) 的組合意義說明。

方程\(x_1+x_2+\dots+x_n=i\)的非負整數解個數。也就是組合數隔板法。

分子的 \(x^i\) 項係數的組合意義為:考慮從 \(1,2,\ldots,n\) 中選若干個和為 \(i\) 的數(每個數只能選一遍)的所有方案,若選了奇數個數貢獻為 \(-1\),若選了偶數個數貢獻為 \(1\)

\(f_{i,j}\) 表示選 \(i\) 個數和為 \(j\) 的方案數。

由於選擇的數兩兩不同,第一維的大小是 \(O(\sqrt k)\) 的。

轉移有兩種方式:

  1. 揹包裡的所有數加一,\(f_{i,j}\gets f_{i,j-i}\)

  2. 揹包裡的所有數加一,並向背包中放入一個體積為 \(1\) 的物品,\(f_{i,j}\gets f_{i-1,j-(i-1)-1}\)

但這樣算可能會出現體積大於 \(n\) 的物品。具體來說,當 \(j\ge n+1\) 時,會有 \(f_{i-1,j-n-1}\) 種不合法的方案,需要減去。

計算完 dp 之後,分子的 \(x^i\) 項係數即為 \(\sum\limits_{j\ge0}(-1)^jf_{j,i}\)

最後把分子和分母卷積起來即可,總時間複雜度為 \(O(n+k\sqrt k)\)\(O(n\log p+k\sqrt k)\)(取決於計算組合數與逆元的方式)。

CO int N=1e5+10;
int fac[2*N],ifac[2*N];
int f[2][N];

IN int C(int n,int m){
	return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
	int n=read<int>(),m=read<int>();
	fac[0]=1;
	for(int i=1;i<=n+m;++i) fac[i]=mul(fac[i-1],i);
	ifac[n+m]=fpow(fac[n+m],mod-2);
	for(int i=n+m-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
	int ans=C(n-1+m,m);
	int o=0;
	f[o][0]=1;
	for(int i=1,sum=1;sum<=m;++i,sum+=i){ // at least sum
		o^=1,memset(f[o],0,sizeof(int)*sum);
		for(int j=sum;j<=m;++j){
			f[o][j]=add(f[o^1][j-i],f[o][j-i]);
			if(j>=n+1) f[o][j]=add(f[o][j],mod-f[o^1][j-n-1]);
			ans=add(ans,mul(mul(i&1?mod-1:1,f[o][j]),C(n-1+m-j,m-j)));
		}
	}
	write(ans,'\n');
	return 0;
}

https://www.cnblogs.com/yinwuxiao/p/9741568.html

講道理\(\prod_{i=1}^n(1-x^i)\)這麼好看,應該可以快速求。

取個\(\ln\),奇妙的事情發生了。

\[\ln\prod_{i=1}^n(1-x^i)=\sum_{i=1}^n\ln(1-x^i)\\ =\sum_{i=1}^n\sum_{j=1}^{\infty}\frac{1}{j}(x^j)^i\\ =\sum_{i=1}^nx^i\sum_{j|i}\frac{1}{j} \]

  1. \(\ln(1-x)=x+\frac{1}{2}x^2+\frac{1}{3}x^3+\dots\)

  2. \(\ln(1-x^2)=x^2+\frac{1}{2}x^4+\frac{1}{3}x^6+\dots\)

觀察這兩行應該就能找出規律。

做個指數乘法卷積(狄利克雷)卷積然後\(\exp\)回去就好了。

時間複雜度\(O(n\log n)\),與\(k\)無關,相當優秀。