题解:P10380 「ALFR Round 1」D 小山的元力
思路分析
考虑先求出 \(b_i\),枚举每个 \(i\) 与 \(a_i\) 来求出 \(b_i\)。发现当前的 \(b_i\) 会对答案造成多次贡献,考虑求出造成多少贡献,原本的 \(n\) 个元素分为 \(m\) 组被转化为 \(n-a_i\) 个元素被分为 \(m-1\) 组有多少方案,可以为空。则答案就是 \(\binom{n-a_i+m-2}{m-2}\),详见插板法。
进一步考虑,发现对于第 \(j\) 堆元素,其答案为:
\[\left(\sum\limits_{a_i=0}^{n} a_i \times \binom{n-a_i+m-2}{m-2}\right) \times j!
\]
由于 \(a_i=0\) 的时候没有值,所以 \(a_i\) 的初始值可以为 \(1\)。发现对于每一个 \(j\),前面的式子是一样的,所以可以将其变为:
\[\left(\sum\limits_{a_i=1}^{n} a_i \times \binom{n-a_i+m-2}{m-2}\right) \times \left(\sum\limits_{j=1}^{m}j!\right)
\]
而前面的式子可以枚举每个 \(a_i\) 进行求解,后面可以直接预处理。
注意:
-
求组合数的时候记得用 Lucas,因为如果直接求组合数,则求乘法逆元的时候,分母的阶乘有可能是 \(p\) 的倍数,此时就会返回 \(0\),而 Lucas 则保证 \(x,y<p\),所以 \(x,y\) 的阶乘也一定不是 \(p\) 的倍数,此时求组合数就不会出问题。
-
阶乘的初始值要从 \(0\) 开始赋值,即 \(fact_0\) 要赋值为 \(1\),因为 \(\binom{n}{n}\) 等于 \(1\) 而不是 \(0\)。
AcCode
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e6+10;
int n,m,p,ans1,ans2;
int fact[N];
inline int read(){
int t=0,f=1;
register char c=getchar();
while(c<'0'||c>'9') f=(c=='-')?(-1):(f),c=getchar();
while(c>='0'&&c<='9') t=(t<<3)+(t<<1)+(c^48),c=getchar();
return t*f;
}
void solution(){
/*
将 n-i 个元素分到 m-1 组中
每组可以为空
有 n-i+m-1 个元素,插 m-2 个板
有 n-i+m-2 个空隙
ans=C_{n-i+m-2}^{m-2}
对于第 j 组,每组答案为:
i*C_{n-i+m-2}^{m-2} (i ∈ [1,n]) * j!
对于前面的式子,用 O(n) 求出
后面的预处理得到
最后的答案为:
cgm(i*C_{n-i+m-2}^{m-2}) * cgm(j!) (i∈[1,n] j∈[1,m])
*/
}
int ksm(int x,int y){
int sum=1;
while(y){
if(y&1) sum=sum*x%p;
x=x*x%p;
y>>=1;
}
return sum;
}
int C(int x,int y){
if(x<y) return 0;
return fact[x]*ksm(fact[x-y],p-2)%p*ksm(fact[y],p-2)%p;
}
int lucas(int x,int y){
if(y==0) return 1;
return C(x%p,y%p)*lucas(x/p,y/p)%p;
}
void init(){
fact[0]=1;
for(int i=1;i<=n+m+2;i++) fact[i]=fact[i-1]*i%p;
}
signed main(){
n=read(),m=read(),p=read();
if(m==1){cout<<n%p;return 0;}
init();
for(int i=1;i<=n;i++) ans1=(ans1+i*lucas(n-i+m-2,m-2)%p)%p;
for(int i=1;i<=m;i++) ans2=(ans2+fact[i])%p;
cout<<ans1*ans2%p;
return 0;
}

浙公网安备 33010602011771号