1. 程式人生 > 其它 >Codeforces 1089I - Interval-Free Permutations(析合樹計數)

Codeforces 1089I - Interval-Free Permutations(析合樹計數)

析合樹計數

Codeforces 題面傳送門 & 洛谷題面傳送門

首先題目中涉及排列的 interval,因此可以想到析合樹。由於本蒟蒻太菜了以至於沒有聽過這種神仙黑科技,因此簡單介紹一下這種資料結構:我們注意到排列的區間有一個性質:對於排列中的兩段區間 \(X,Y\),如果它們有交,那麼必然有 \(X\cap Y,X\cup Y,X\setminus(X\cap Y),Y\setminus(X\cap Y)\) 四個集合均為區間,也就是說連續段之間只有包含沒有相交關係,因此它們可以表示為一棵樹形結構。

我們考慮用一棵根節點為區間 \([1,n]\),葉子節點為每個長度為 \(1\) 的區間的樹表示這個樹形結構,對於每個區間我們定義它的本原連續段為極大的、彼此之間不存在部分相交的連續段,舉個例子,排列 \([5,1,4,2,3]\)

有兩個本原連續段:\([5],[1,4,2,3]\)——顯然我們能夠找到這樣的連續段組成的集合。那麼我們就令這個區間的兒子為這些本原連續段們,繼續遞迴下去即可建出這棵樹。由於這棵樹的葉子節點恰有 \(n\) 個,因此這棵樹的節點數也是線性的。

考慮將這棵樹的節點分分類,由於每個節點的兒子們都是一個個區間,因此我們可以將它們離散化成一個個在 \([1,\text{兒子個數}]\) 之內的數,我們稱這樣得到的排列為兒子排列,手玩幾組資料即可發現對於每個點而言,它的兒子排列總共只有兩種型別,否則就不滿足“本原連續段”的定義了:

  • 兒子排列從左到右恰好為 \(1,2,3,\cdots,\text{兒子個數}\)
    或者 \(\text{兒子個數},\cdots,3,2,1\),我們稱這樣的點為合點
  • 兒子排列中除了整個區間和長度為 \(1\) 的子區間不存在任何其他連續段,我們稱這樣的點為析點

比方說排列 \([9,1,10,3,2,5,7,6,8,4]\) 建出樹來如下圖所示:

析合樹有以下性質:

  • 每個析點兒子個數一定 \(\ge 4\),因為任何長度為 \(3\) 的排列都存在非平凡連續段
  • 如果我們指定一棵樹上每個節點的析合性,並滿足析點兒子個數 \(\ge 4\),合點兒子個數 \(\ge 2\),那麼一定存在某個排列對應這棵樹

回到此題來,此題等價於求兒子個數為 \(n\),且根為析點的排列個數 \(f_n\)

,直接求不太容易,因此考慮正難則反,那總排列數減去不合法的排列個數,前者就是 \(n!\),後者可以分情況討論:

  1. 根是析點,那麼我們可以列舉根節點的兒子個數 \(c\ge 4\),那麼我們要將 \(n\) 個節點劃分成 \(c\) 個區間,每個區間內的元素隨便亂排,最後還要將這 \(c\) 個區間排成一列滿足不存在非平凡區間,很顯然我們可以將這個任務分成兩部分,劃分兒子和確定兒子排列,後者方案數顯然就是 \(f_c\),前者可以設一個 \(s_{i,j}\) 表示將 \(i\) 個節點劃分成 \(j\) 段的方案數,顯然有 \(s_{i,j}=\sum\limits_{k<i}s_{i-k,j-1}·k!\)
  2. 根是合點,那麼我們不妨假設根節點的兒子排列為 \(1,2,3,\cdots\),對於單調遞減的情況乘個 \(2\) 即可,根據合點的定義必然存在某個字首 \(i\) 滿足 \(p[1...i]\) 恰好為 \([1,i]\) 的排列,我們就考慮列舉這個最小的 \(i\),記 \(g_i\) 為長度為 \(i\) 的、且存在某個長度不等於 \(i\) 的字首 \(p[1...j]\)\([1,j]\) 的排列的排列 \(p\) 的個數,那麼有 \(g_i=i!-\sum\limits_{j<i}g_j(i-j)!\),根是合點的總數也就自然是 \(2g_n\)

簡單遞推一下即可,複雜度三方。

const int MAXN=400;
int mod,fac[MAXN+5],ifac[MAXN+5],dp[MAXN+5],s[MAXN+5][MAXN+5],f[MAXN+5];
void init(int n){
	for(int i=(fac[0]=ifac[0]=ifac[1]+1);i<=n;i++) ifac[i]=1ll*ifac[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod,ifac[i]=1ll*ifac[i-1]*ifac[i]%mod;
	f[1]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++) f[i]=(f[i]+1ll*f[j]*fac[i-j])%mod;
		f[i]=(fac[i]-f[i]+mod)%mod;
	} s[0][0]=1;
	for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) for(int k=1;k<=i;k++)
		s[i][j]=(s[i][j]+1ll*s[i-k][j-1]*fac[k])%mod;
	dp[2]=(dp[1]=(dp[3]=0)+1)+1;
	for(int i=4;i<=n;i++){
		int sum1=0,sum2=0;
		for(int j=1;j<i;j++) sum1=(sum1+1ll*f[j]*fac[i-j])%mod;
		for(int j=4;j<i;j++) sum2=(sum2+1ll*dp[j]*s[i][j])%mod;
		int sub=(2ll*sum1+sum2)%mod;dp[i]=(fac[i]-sub+mod)%mod;
	}
}
int main(){
	int qu;scanf("%d%d",&qu,&mod);init(MAXN);
	while(qu--){
		int n;scanf("%d",&n);
		printf("%d\n",dp[n]);
	}
	return 0;
}