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改好了么?
这题挺不错的,对组合中的常用等式:C(m+1,i) = C(m,i) + C(m,i-1) 的运用比较好。
另外关于这场比赛(这场gym),A题的题解此处推荐一篇博客:https://www.cnblogs.com/iLex/p/16213006.html#_label0