题解 P2183 【[国家集训队]礼物】

eee_hoho

2019-06-26 18:22:09

Solution

题不是很难,黑题应该~~虚高~~了 看到题之后,应该就能写出式子 $$ans=\prod_{i=1}^{m}C_{n-\sum_{j=1}^{i-1}w_j}^{w_i}(mod\ P)$$ 式子看起来很麻烦,其实很好想 当我们给第一个人送礼物的时候,我们有$n$个礼物,要选$w_1$个,方案就有$C_n^{w_{1}}$种 然后给第二个人送礼物的时候,我们剩下$n-w_1$个礼物,要选$w_2$个,方案有$C_{n-w_1}^{w_2}$种 就这样一直送到第$m$个人,而根据乘法原理,就得到这个式子了 这道题似乎就做完了,但在算$C_n^m(mod\ p_i^{c_i})$时又遇到了瓶颈:$p_i^{c_i}$不是质数 既然不是质数,我们就不能用$Lucas$定理来求,这就需要用到$ExLucas$了 虽然是扩展的,但和$Lucas$完全沾不上边 我们观察到题目给了$P=\prod _{i=1}^{t}p_{i}^{c_i}$ 而$p_i$是质数,那么所有的$p_i^{c_i}$都是互质的 那么我们只要对于每个$p_i^{k=c_i}$求出其$C_n^m\ mod\ p_i^k$的值,然后用**中国剩余定理**就可以算出最小正整数解 问题转化成了如何快速的求$C_n^m\ mod\ p^k$ 把组合数展开,我们得到 $$\frac{n!}{m!(n-m)!}\ mod\ p^k$$ 那么只要快速求出模意义下的阶乘就好了 如果我们要求$x!\ mod\ p^k$,假设$x=17,p=2,k=3$,观察一下式子 $$17!=1\times2\times3\times4\times5\times6\times7\times8\times9\times10\times11\times12\times13\times14\times15\times16\times17$$ 化式子$17!=$ $$2\times4\times6\times8\times10\times12\times14\times16\times1\times3\times5\times7\times9\times11\times13\times15\times17$$ $$(2\times1)\times(2\times2)\times(2\times3)\times…\times(2\times8)\times(1\times3\times5\times…\times17)$$ $$2^8\times(1\times2\times3\times…\times8)\times(1\times3\times5\times…\times17)$$ $$2^8\times8!\times(1\times3\times5\times7\times9\times11\times13\times15\times17)$$ 而在模$p^k=2^3=8$意义下$1\times3\times5\times7=9\times11\times13\times15$ 所以式子就变成了 $$2^8\times8!\times(1\times3\times5\times7)^2\times17$$ 那么$x!$就变成了$p^n\times t!\times(a_1\times a_2\times…a_m)^r\times a_{m+1}\times…a_{q}$ 可以看出$n=t=\left \lfloor \frac{x}{p} \right \rfloor,r=\left \lfloor \frac{x}{p^k} \right \rfloor$,其中$t!$可以一直递归求解,然后后面的暴力算 这样这个题就做完啦QAQ **Code** ``` cpp #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #define int long long using namespace std; int p[200000],n,m,P,cnt,z[200000],a[200000],ans; void exgcd(int a,int b,int &x,int &y) //扩欧 { if (!b)x=1,y=0; else { exgcd(b,a%b,x,y); int t=x; x=y; y=t-a/b*y; } } int mypow(int a,int x,int p) //快速幂 { int s=1; while (x) { if (x&1)s=s*a%p; a=a*a%p; x>>=1; } return s; } int fac(int n,int a,int b) //求阶乘 { if (!n)return 1; int s=1; for (int i=1;i<=b;i++) //处理a1*a2*…am if (i%a!=0) s=s*i%b; s=mypow(s,n/b,b); for (int i=1;i<=n%b;i++) //处理am+1*…aq if (i%a!=0) s=s*i%b; return s*fac(n/a,a,b)%b; //递归求解 } int inv(int a,int b) //求逆元 { int x,y; exgcd(a,b,x,y); return (x%b+b)%b; } int C(int m,int n,int a,int b) //处理组合数 { int nn=fac(n,a,b),mm=fac(m,a,b),nm=fac(n-m,a,b),po=0; //求阶乘 for (int i=n;i;i/=a) //处理n^p中的p po+=i/a; for (int i=m;i;i/=a) po-=i/a; for (int i=n-m;i;i/=a) po-=i/a; return nn*mypow(a,po,b)%b*inv(mm,b)%b*inv(nm,b)%b; } signed main() { cin>>n>>m>>P; int pp=P; for (int i=2;i*i<=P;i++) //分解 if (pp%i==0) { int x=i; z[++cnt]=i; while (pp%x==0)x*=i; x/=i; p[cnt]=x; pp/=x; } if (pp!=1)p[++cnt]=pp,z[cnt]=pp; for (int i=1;i<=cnt;i++) a[i]=C(m,n,z[i],p[i]); for (int i=1;i<=cnt;i++) //中国剩余定理 { int pi=P/p[i],x,y; exgcd(pi,p[i],x,y); ans=((ans+pi*a[i]*x%P)+P)%P; } cout<<ans<<endl; return 0; } ```