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

 

posted @ 2020-10-29 10:10  Hzoi_joker  阅读(375)  评论(0编辑  收藏  举报