1. 程式人生 > 其它 >2020CCPC秦皇島H.Holy Sequence

2020CCPC秦皇島H.Holy Sequence

題目

好久沒寫題解了,做了個計數感覺有點妙妙,就來口胡一波。

由於我們要對於每一個\(t\)單獨計算答案,所以必然要先列舉\(t\),考慮一個\(n\)-holy序列的字首最大值每次最多隻能增加\(1\),必然可以化為\(1......2......3......4......5.......t\)的形式。我們列舉\(t\)第一次出現的位置為\(i\)\(i\)前面的方案數可以用一個dp預處理出來。觀察這個dp的轉移發現其實就是第二類斯特林數

這樣\(t\)出現的位置必然在\(i\)以及\(i\)之後,考慮一個經典的恆等式\(x^2=2\times \binom{x}{2}+\binom{x}{1}\)

。以計算\(\binom{x}{2}\)為例,我們完全可以欽定兩個位置填\(t\),其餘位置構成一個合法序列即可算貢獻即可,而注意到我們欽定了\(i\)\(t\),那麼\(i\)之後的位置填\(t\)都是合法的,所以我們其實並不是很關心這兩個位置到底在哪裡,在\(i\)後面用組合數隨便欽定兩個位置即可。

最後還需要求出\(i\)\(t\)後面的合法方案數,發現從\(i\)填到\(n\)需要強行從\(t\)開始難以處理,於是考慮從\(n\)倒著向\(i\)搞一個dp,這樣數是從大到小的,並不需要強行令\(t\)作為開頭。

預處理第二類斯特林數和這個反向的\(dp\),就能\(O(n)\)回答一個\(t\)

的答案了。

程式碼

#include<bits/stdc++.h>
#define re signed
const int maxn=3005;
int n,mod,g[maxn][maxn],h[maxn][maxn];
inline int qm(int x) {return x>=mod?x-mod:x;}
inline int calc(int x) {return (1ll*x*(x-1)/2ll)%mod;}
int main() {
	int Test;scanf("%d",&Test);
	for(re int te=1;te<=Test;++te) {
		scanf("%d%d",&n,&mod);
		for(re int i=0;i<=n+1;++i)
			for(re int j=0;j<=n+1;++j)g[i][j]=h[i][j]=0;
		g[0][0]=1;
		for(re int i=0;i<n;i++)
			for(re int j=0;j<=i;++j) {
				if(!g[i][j]) continue;
				g[i+1][j+1]=qm(g[i+1][j+1]+g[i][j]);
				g[i+1][j]=qm(g[i+1][j]+1ll*g[i][j]*j%mod);
			}
		for(re int i=1;i<=n;i++)h[0][i]=1;
		for(re int i=0;i<n;i++)
			for(re int j=n;j;--j) {
				if(!h[i][j]) continue;
				h[i+1][j]=qm(h[i+1][j]+1ll*h[i][j]*j%mod);
				if(j-1>0) h[i+1][j-1]=qm(h[i+1][j-1]+h[i][j]);
			}
		printf("Case #%d:\n",te);
		for(re int t=1;t<=n;++t) {
			int ans=0;
			for(re int i=1;i<=n;i++) {
				if(!g[i-1][t-1]) continue;
				int tot=h[n-i][t];
				if(n-i-1>=0) tot=qm(tot+3ll*h[n-i-1][t]%mod*(n-i)%mod);
				if(n-i-2>=0) tot=qm(tot+2ll*h[n-i-2][t]%mod*calc(n-i)%mod);
				ans=qm(ans+1ll*tot*g[i-1][t-1]%mod);
			}
			printf("%d ",ans);
		}
		puts("");
	}
	return 0;
}