1. 程式人生 > 實用技巧 >CCPC2020 秦皇島 H Holy Sequence

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