CCPC2020 秦皇島 H Holy Sequence
題意:
$a_1……a_n$ 滿足要求當且僅當 $1<=a_i<=n$,且 $max(a_{1},……,a_{i})-max(a_{1},……,a_{i-1})<=1$。
現在問長度為n的所有滿足條件的序列裡面,數字 $i$ 出現次數的平方的和,$n<=3000$,模數現給。
題解:
妙啊。
首先,$x^2$可以理解為有$x$個元素,從這$x$個元素中可重複的有次序的選出兩個數的方案數。
那麼我們可以把選擇方案分成以下幾個情況:
1、選擇數字 $i $第一次出現的位置和之後出現的某個位置
2、選擇數字 $i $第二次出現或之後的兩個位置
3、選擇數字 $i $第一次出現的位置兩次
4、選擇數字 $i$ 第二次或之後的一個位置選擇兩次
這道題有一個很有趣的性質,就是第 $i$ 位往後的方案數與 $1$~$i$ 的最大值有直接關係,所以我們可以先列舉 數字 i 第一次出現的位置,然後算出以 i 打頭,往後一定長度的方案數,數字 i 第一次出現的位置為 j 的方案數,然後根據組合數算出最終答案。
首先,我們先算數字 i 第一次出現的位置為 j 的方案數,設 $f [ i ] [ j ]$ 為數字$i$ 第一次出現的值為 j 的方案數。不難發現答案就是斯特林數,因為資料小,直接遞推即可。
之後,我們去算以 i 打頭,之後長度為 j 且序列合法的方案數 ,設 $H [ i ] [ j ]$為以 $j$ 打頭,之後長度為 $i-1$ 且序列合法的方案數 。
一開始我想轉移方程是
$H [ i ] [ j ] = \sum_{k=1}^{j+1}H [i-1] [k]$
但是這樣是不正確的。因為 $H[i-1][1……j+1]$在前 $i-1$次的轉移中的上限並不是 $j$ ,所以正確的轉移應當是
$H [ i ] [ j ] = H [ i-1 ] [ j ] * j+ H [ i-1 ] [ j+1 ] $
這樣轉移本質上就是讓 $H [ i-1 ] [ j ]$接在第2位上,第一位填 $j$ ,且第2位從 $j$改成 $1$~ $j$ 的總方案。
現在,讓我們考慮第 1 種情況如何轉移。
首先,我們可以利用$f[i-1][j-1]$枚舉出來數字 $j $第一次出現在哪個位置,之後,我們可以利用組合數,在剩下的$n-i$個位置中調出一個代表我們選了在這個位置的$j$,然後剩下的位置的方案數用H[n-i][j]和組合數進行計算。
第2中情況也是這樣
我們還是利用$f[i-1][j-1]$列舉數字j第一次出現的位置,之後在$n-i$個位置中挑出兩個位置為$j$,剩下的位置的方案數用$H[n-i-1][j]$和組合數進行計算。
第3、4中情況同理
值得注意的是,$(x,y)和(y,x)$算兩種選擇方案,所以第1、2種情況要乘以2。
1 #include<cstdlib> 2 #include<cstdio> 3 #include<iostream> 4 #include<cstring> 5 #include<algorithm> 6 #include<cmath> 7 #define N 3005 8 using namespace std; 9 int T,n,p; 10 int F[N][N],G[N][N]; 11 long long ans[N]; 12 long long ksm(long long x,long long z) 13 { 14 long long ans=1; 15 while(z) 16 { 17 if(z&1) 18 { 19 ans=ans*x%p; 20 } 21 x=x*x%p; 22 z>>=1; 23 } 24 return ans; 25 } 26 long long C[N][N]; 27 int main() 28 { 29 scanf("%d",&T); 30 int cnt=0; 31 32 while(T--) 33 { 34 cnt++; 35 scanf("%d%d",&n,&p); 36 memset(ans,0,sizeof(ans)); 37 C[0][0]=1; 38 for(int i=1;i<=n;i++) 39 { 40 C[i][0]=1; 41 for(int j=1;j<=2;j++) C[i][j]=C[i-1][j]+C[i-1][j-1],C[i][j]%=p; 42 } 43 F[1][1]=1; 44 F[0][0]=1; 45 for(int i=1;i<=n;i++) 46 { 47 for(int j=1;j<=i;j++) 48 { 49 F[i][j]=(1ll*F[i-1][j]*j%p+F[i-1][j-1])%p; 50 } 51 } 52 for(int i=n;i;i--) G[1][i]=1; 53 for(int i=2;i<=n;i++) 54 { 55 for(int j=1;j<=n;j++) 56 { 57 G[i][j]=(1ll*G[i-1][j]*j%p+G[i-1][j+1])%p; 58 } 59 } 60 for(int i=1;i<=n;i++) 61 { 62 int tmp1=0,tmp2=0; 63 for(int j=1;j<=i;j++) 64 { 65 if(n-i-1>0) tmp1=(1ll*G[n-i-1][j]*j+G[n-i-1][j+1])%p; 66 else if(n-i-1==0) tmp1=1; 67 if(n-i-2>0) tmp2=(1ll*G[n-i-2][j]*j+G[n-i-2][j+1])%p; 68 else if(n-i-2==0) tmp2=1; 69 ans[j]=(1ll*ans[j]+3ll*F[i-1][j-1]%p*tmp1%p*C[n-i][1]%p)%p; 70 ans[j]=(1ll*ans[j]+2ll*F[i-1][j-1]%p*tmp2%p*C[n-i][2]%p); 71 ans[j]=(1ll*ans[j]+1ll*F[i-1][j-1]*G[n-i+1][j]%p)%p; 72 } 73 } 74 printf("Case #%d:\n",cnt); 75 for(int i=1;i<n;i++) 76 { 77 printf("%d ",ans[i]); 78 } 79 printf("%d",ans[n]); 80 printf("\n"); 81 82 } 83 return 0; 84 }View Code