gym103637F (2019-2020 10th BSUIR Open Programming Championship. Semifinal) 题解

题意:从n个数中可重复地随机抽出m个数得到数列q,排序后q[d]>k则q合法, 求q合法的概率,需要输出d~n范围内的每个m对应的方案数。

 

不难得到对与固定的一个m (d<=m<=n),有P = Σd-1i=0C(m,i)ki(n-k)m-i  / n!  (i为抽到的不大于k的数的个数)。用这个式子计算需要O(d)的复杂度。

难点在于需要回答n-d+1个询问,如何快速计算呢?

 

定义ans[m] = Σd-1i=0C(m,i)ki(n-k)m-i  (P的分子,含义为对于固定的m,合法的q的数量)

ans[m+1]  =  Σd-1i=0C(m+1,i)ki(n-k)m+1-i  

因为C(m+1,i) = C(m,i) + C(m,i-1) (注意:特别地,当i等于0的时候,直接套公式有C(m+1,0) = C(m,0) + C(m,-1),此时可以认为C(m,-1) = 0,即有C(m+1,0) = C(m,0) = 1)

所以ans[m+1]  =  Σd-1i=0 (C(m,i) + C(m,i-1)) ki(n-k)m+1-i  = Σd-1i=0 C(m,i) ki(n-k)m+1-i  + Σd-1i=0 C(m,i-1) ki(n-k)m+1-i                                 (1)

观察这两项,

第一有Σd-1i=0 C(m,i) ki(n-k)m+1-i   = Σd-1i=0 C(m,i) ki(n-k)m-i * (n-k)  = ans[m] * (n-k) ;

第二有 Σd-1i=0 C(m,i-1) ki(n-k)m+1-i = Σd-1i=1 C(m,i-1) ki(n-k)m+1-i = Σd-2i=0 C(m,i) ki+1(n-k)m-i = k * (Σd-2i=0 C(m,i) ki(n-k)m-i ) = k * ( ans[m]-C(m,d-1) kd-1(n-k)m-d+1 )

(上式第一个等号是因为可以认为C(m,-1)=0;第二个等号是因为作了下标替换,实际上等号两边的项一一对应;

 

汇总上面两项,有递推式:ans[m+1] = ans[m] * (n-k)  +  k * ( ans[m]-C(m,d-1) kd-1(n-k)m-d+1 ) = n*ans[m]  + k*C(m,d-1) kd-1(n-k)m-d+1 

最后让所有ans都除以n!就得到概率,就可以ac本题了。

 

更重要的是,我们要分析上面式子的含义,不然感觉这题只是推个公式,太机械了。

上面的式子的重要基础是组合中的常用等式:C(m+1,i) = C(m,i) + C(m,i-1) ,含义为从m+1个数里选i个数的方案数,等于“必不选第一个数,从剩下的m个数里选i个数的方案数” + “必选第一个数,从剩下的m个数里选i-1个数的方案数” 。

式(1)等式右端的两项分别有以上含义,具体来说,

第一项为ans[m] * (n-k) ,表示m+1个数中的第一个数必选大于k的,然后再在剩下的m个数里选至多d-1个不大于k的数,以保证m+1个数中有至多d-1个不大于k。

第二项为k * ( ans[m]-C(m,d-1) kd-1(n-k)m-d+1 ) , 表示m+1个数中的第一个数必选不大于k的,然后再在剩下的m个数里选至多d-2个不大于k的数,以保证m+1个数中有至多d-1个不大于k。

 

AC代码:

 

 1 //exeCreate 2022-7-14 12:34:18    
 2 
 3 #include<stdio.h>
 4 #include<stdlib.h>
 5 #include<iostream>
 6 #include<algorithm>
 7 #include<string>
 8 #include<string.h>
 9 #include<cmath>
10 #include<vector>
11 #include<set>
12 #include<queue>
13 
14 #define rep(i,a,b) for(int i=(a);i<=(b);++i)
15 #define per(i,a,b) for(int i=(a);i>=(b);--i)
16 #define fi first
17 #define se second
18 #define mp make_pair
19 #define all(x) x.begin(),x.end()
20 
21 using namespace std;
22 typedef long long ll;
23 typedef pair<ll,ll> PII;
24 typedef pair<int,int> Pii;
25 const int maxn=3e5+10,maxm=maxn;
26 const ll mod=998244353,inf=0x3f3f3f3f;
27 
28 /*组合数*/
29 ll quickPow(ll a,ll b){
30     ll ret=1;
31     a%=mod;
32     while(b){
33         if(b&1) ret=ret*a%mod;
34         a=a*a%mod;
35         b>>=1;
36     }
37     return ret;
38 }
39 ll fac[maxn]={1},facinv[maxn];    //支持C(5000,2500)
40 void Cinit(){
41     rep(i,1,maxn-1) fac[i] = fac[i-1]*i%mod;
42     rep(i,0,maxn-1) facinv[i] = quickPow(fac[i],mod-2);
43 }
44 ll C(int n,int m){
45     return fac[n]*(facinv[n-m]*facinv[m]%mod)%mod;
46 }
47 
48 ll ans[maxn];
49 int main()
50 {
51     Cinit();
52     int n,d,k;    cin>>n>>d>>k;
53 
54     ans[d]=0;
55     int m=d;
56     rep(i,0,d-1){
57         ans[d] = (ans[d] + C(m,i)*quickPow(k,i)%mod*quickPow(n-k,m-i))%mod;
58     }
59 
60     rep(m,d,n-1){
61         ans[m+1] = (n*ans[m]%mod-C(m,d-1)*quickPow(k,d-1)%mod*quickPow(n-k,m-d+1)%mod*k%mod + mod)%mod;
62     }
63     
64     rep(i,d,n) {
65         ans[i] = ans[i]*quickPow(quickPow(n,i),mod-2)%mod;
66         printf("%lld\n",ans[i]);
67     }
68 }
69 //maxn改好了么?
View Code

 

这题挺不错的,对组合中的常用等式:C(m+1,i) = C(m,i) + C(m,i-1) 的运用比较好。

 

另外关于这场比赛(这场gym),A题的题解此处推荐一篇博客:https://www.cnblogs.com/iLex/p/16213006.html#_label0

posted @ 2022-07-15 19:53  Laozhu1234  阅读(126)  评论(0编辑  收藏  举报