51nod 1829 函数 (第二类斯特林数+逆元) 屎上最详细题解

来源:互联网 发布:达盖尔先锋团 知乎 编辑:程序博客网 时间:2024/06/04 20:06

传送门:51nod 1829



题目大意

对于本蒟蒻来说,做这道题也是绞尽乳汁,啊,呸……绞尽脑汁。这道题所用到的知识点很多,希望下面我可以把它说明白,涉及的知识点较多,希望大家耐心阅读。



前置技能

1.第二类斯特林数。第二类Stirling数 S(n,m) 表示将 n 个不同的元素分成 m 个非空集合的方案数。


2.逆元。或者说乘法逆元,除以一个数取模等于乘以这个数的逆元取模,即设 c 为 b 的逆元,则 (a/b)%m=(a*c)%m 。为什么要用逆元呢?直接相除取模不就可以了吗?如果仅有一步的时候是正确的,但是如果对一个已经取模的数再除以 b 取模呢?答案就可能不正确了,这时候就需要用逆元来做。


3.同余模定理。这里只说一下乘法公式,其他的类似:若 n=a*b ,则 n%m = ( a%m * b%m )%m .



思路

所谓映射,就是对于 B 中任何一个元素在 A 中都有元素与之对应,而 A 中的一个元素只能对应 B中的一个元素。


看到这道题的时候有没有想到第二类斯特林数呢?也就是将 n 个不同的元素分成 m 个非空集合的方案数。当然由于分成的集合是无差别的,但是映射的时候是有差别的,所以还要乘以 B 中元素的全排列,即 m! 。


之前只知道第二类斯特林数有个递推公式:S(i,j)=jS(i1,j)+S(i1,j1)交了一发妥妥的TLE。一百度才知道原来还有个公式:

S(n,m)=1m!i=0m(1)iC(m,i)(mi)n
之前说过,答案应该是 m! * S(n,m) ,所以化简后就是:

ans=i=0m(1)iC(m,i)(mi)n
现在分析一下上面这个式子,主要是计算组合数 C(m,i) 和一个幂运算 (m-i)^n,由于结果是要模 1e9+7 的,所以要在求和的每一步都取模。这时候就用到了前面说的同余模定理: ( C(m,i) * (m-i)^n ) % mod = ( C(m,i) % mod * (m-i)^n % mod) % mod .


其中 (m-i)^n % mod 好说,可以用快速幂取模解决C(m,i) 取模时要注意由于组合数公式用到了除法,所以这里要用逆元,即除以一个数取模等于乘以这个数的逆元取模。


如上图,计算 C(m,n) 要计算出 m! 以及 n! * (m-n)! 模 mod 的逆元,可以分别求 n! 模 1e9+7 的逆元和 (m-n)! 模 mod 的逆元。由于 n! 和 (m-n)! 都和 mod 互质并且 mod 为质数,所以可以用费马定理求逆元为: n! ^ (mod-2) 和 (m-n)! ^ (mod-2)。 mod = 1e9+7.


我们另 fac[i] 表示 i 的阶乘,inv[i] 表示 i 的阶乘的逆元,另 mod=1e9+7,则 fac[i+1] 的逆元为 fac[i+1] ^ (mod-2) ,即   (fac[i]^(mod-2) * (i+1)^(mod-2))% mod,而 fac[i] 的逆元为 fac[i]^(mod) ,所以 inv[i+1] = ( inv[i] * (i+1)^(mod-2) ) % mod . 而我们知道 inv[1] = 1^(mod-2) = 1 ,所以就可以根据公式得到每个阶乘的逆元了。


然鹅……由于以上逆元的公式包含求幂操作,所以会超时Orz……所以我们可以逆着推, inv[i] = ( inv[i+1] / (i+1)^(mod-2) ) % mod , 要除以(i+1)^(mod-2),就等于乘以它的逆元……由于逆元是相互的,即 i+1 模 mod 的逆元为(i+1)^(mod-2) ,所以(i+1)^(mod-2)  模 mod 的逆元为 i+1。 inv[i]=( inv[i+1] * (i+1) ) % mod.


然后求组合数解决了,求快速幂也解决了,就可以敲代码了……本来想简单的解释清楚的,没想到又说了这么多。。。真心搞不懂大牛们都是怎么想出的题解……果真是自己弱,Orz……



代码

#include<stdio.h> #include<string.h>#define MAXN 1000010typedef long long LL;LL mod=1e9+7;LL fac[MAXN],inv[MAXN]; //fac表示阶乘,inv表示阶乘的逆元 LL pow(LL a,LL b){ //快速幂取模 LL r=1,base=a;while(b){if(b&1) r=r*base%mod;base=base*base%mod;b>>=1;}return r;}void init(){ //求每个数的阶乘和阶乘的逆元 int i;fac[0]=1;for(i=1;i<MAXN;i++) //求阶乘 fac[i]=fac[i-1]*i%mod;inv[MAXN-1]=pow(fac[MAXN-1],mod-2); //根据费马定理求最后一个数阶乘的逆元 for(i=MAXN-2;i>=0;i--) //根据公式求每个阶乘的逆元 inv[i]=inv[i+1]*(i+1)%mod;/* 因为用到快速幂,会超时 inv[1]=1;for(i=2;i<MAXN;i++)inv[i]=(inv[i-1]*pow(i,mod-2))%mod;*/}LL C(LL n,LL m){ //根据组合数公式求组合数 if(m>n) return 0;if(m==0) return 1;return fac[n]*inv[m]%mod * inv[n-m]%mod; //利用了同余模定理 }int main(){init();int i,n,m,e;LL ans=0;e=1;scanf("%d%d",&n,&m);for(i=0;i<=m;i++){ //结果是很多数之和 ans+=C(m,i)*pow(m-i,n)%mod*e;ans%=mod;e*=-1;}printf("%lld\n",(ans+mod)%mod);//防止是负数 return 0;}

原创粉丝点击