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 }