排列组合
Posted shzr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排列组合相关的知识,希望对你有一定的参考价值。
排列组合
摘要中的问题的答案是$int$;
组合:$C_n^k=frac{n!}{k!(n-k)!}$
排列:$A_n^k=frac{n!}{k!}$
求组合数的方法看起来好像没有什么价值,但是事实上还是挺重要的。
1.暴力求就不用介绍了吧,一般只适用于数据范围非常小的题目;
2.取模或不取模都可以用的方法:$C_n^m=C_{n-1}^{m-1}+C_{n-1}^m$.这个做法的思路是这样的:考虑新加进来的第$n$个数的影响,如果选它,那么前$n-1$个中只好少选一个,如果不选它,那么前面就得把$m$个选够,其实是一种动态规划.因为加法可以直接取模,所以对于取模的题目也是非常好用的.注意如果某道题要求采用高精度,就要谨慎的考虑要不要用这种方法了,因为加上高精度之后复杂度会变得非常高,空间开销也非常大.
3.如果要求取模:预处理阶乘和逆元,$O(N)$预处理,$O(1)$出解,效率非常棒.如果$n$的范围比$p$大很多,可以考虑使用$Lucas$定理;
4.最坑人的一种:不要求取模,数据范围还挺大的,此时最好,也只能用高精度了.用高精度也是讲求技巧的,如果直接套用公式就需要做很多高精度乘除,而高精度除法的计算效率非常低下.但是没有关系,虽然组合数可能很大,但是它进行唯一分解之后一定是可以用普通的数组存下来的,为什么?因为组合数的所有因子都是之前在$n,k$中出现过的,所以不会太大.而且因为组合数最终是一个整数,所以所有分母上出现的因子都可以在因子中找到并提前除去,虽然除因子的复杂度不是很低,但是总比用高精度除快多了.来一份这种做法的板子吧:
1 void add (int x,int v) 2 { 3 for (R i=2;i*i<=x;++i) 4 while(x%i==0) y[i]+=v,x/=i; 5 if(x!=1) y[x]+=v; 6 } 7 8 void mul (int x) 9 { 10 c[0]+=3; 11 for (R i=1;i<=c[0];++i) 12 c[i]*=x; 13 for (R i=1;i<=c[0];++i) 14 c[i+1]+=c[i]/10,c[i]%=10; 15 while(c[ c[0] ]==0&&c[0]) c[0]--; 16 } 17 18 void print() 19 { 20 for (R i=c[0];i>=1;--i) 21 printf("%d",c[i]); 22 } 23 24 for (R i=2;i<=g;++i) add(i,1); 25 for (R i=2;i<=k;++i) add(i,-1); 26 for (R i=2;i<=g-k;++i) add(i,-1); 27 28 c[0]=c[1]=1; 29 for (R i=2;i<=1000;++i) 30 for (R j=1;j<=y[i];++j) 31 mul(i); 32 print();
至于排列组合的题目就先咕咕咕了.
以上是关于排列组合的主要内容,如果未能解决你的问题,请参考以下文章