排列组合及计数
生活中我们总是少不了组合数问题
今天我就来讲一讲有关组合数的问题
首先看一看全排列问题
先看一个栗子:
我给你3个颜色不同的球,让你算出一共有多少种排列
我们画一下:
如图所示,答案一共6种
那么这个6又是怎么来的呢?
我们来看一下下面这个过程:
假设第一步我们选了红色
那么第二步,我们就只剩下蓝色和黄色可选。
第二步如果我选了蓝色,那么第三步我就只能选黄色。
反之,如果我选了黄色,第三步我就只能选蓝色(莫名像递归)
合起来,在第一次选红色的情况下,我们一共可以得到两个不同的排列
当然,第一次也可以选蓝色或者是黄色,所以答案有2+2+2=2*3=6种
用一句话来总结,就是:
如果有n种不同颜色的小球,那么
第一次选择有n种,第二次选择有n-1种......最后一次选择有1种可能。
所以一共有n x(n-1)x(n-2)x ……x 1种方案
用一棵树表示上面的栗子(n=3)就是这样:
由此,我们可以得到全排列公式:
这就是组合去重
选取方案数:(C)
解决了全排列问题,我们看另一个经典问题:
一共有n个人,我们要从中选取m个人,求方案数
我们思路往往是这样的:
选第一个人有n种可能性,第二个人有(n-1)种可能性……
但显然不对,为什么呢?
我们还是列举一下(n=4,m=3,不同颜色代表一个人):
我们会发现,1,4,7,10,19,22号其实是一种方案
所以方案数实际上要除以6
喂喂喂,我听不懂啦!说人话!
。。。好吧,我来举个栗子
刚才是4个人(A,B,C,D),我们要选3个人
那么我们第一次有4种选择,第二次有3种选择,第三次有2种选择
乘起来是24
但不同于全排列的是,我第一个人选A与第二个人选A,第三个人选A并没有什么不同
也就是说,同样是(A,B,C)这种方案,我们选取时,会被选取6(m!)次
(A,B,C)(A,C,B)(B,A,C)(B,C,A)(C,A,B)(C,B,A)
所以我们要去重
所以
好啦,轻松一下,我们玩个游戏
看下面这个三角形:
你能发现什么呢?
大多数人都能发现,第n行第m个数=第n-1行第m-1个数+第n-1行第m个数(v[n][m]=v[n-1][m-1]+v[n-1][m])
下面我们把图改一下,标上编号:
我们会发现v[n][m]居然=
那么组合数是不是具有递推性呢?
我们来证明一下:
式1=
式2=
式3=
设式4=式1-式3,
则式4=
=
得证。(看不懂的请跳过,背公式就行)
那么这有用什么用呢?
想想看,如果你要求一个区间内的所有组合数,那么你按公式的话,每次要算m*2次
但如果你用递推组合数,算一次加法就好了
(如果n,m没有大到离谱,甚至可以省掉逆元)
快得很
想要例题出门左转组合数问题(noip2016Day2T1)
我这里就贴代码了:
#include<iostream>
#include<cstdio>
using namespace std;
int x[2005][2005],a,b,c,n,m,t,k,ans[2005][2005];
int main()
{
scanf("%d%d",&t,&k);
x[1][1]=1;
for(a=0;a<=2000;a++)
{
x[a][0]=1;
}
for(a=2;a<=2000;a++)
{
for(b=1;b<=a;b++)
{
x[a][b]=x[a-1][b-1]+x[a-1][b];
x[a][b]%=k;
}
}
for(a=2;a<=2000;a++)
{
for(b=1;b<=a;b++)
{
ans[a][b]=ans[a-1][b]+ans[a][b-1]-ans[a-1][b-1];
if(x[a][b]==0)
{
ans[a][b]++;
}
}
ans[a][a+1]=ans[a][a];
}
for(a=1;a<=t;a++)
{
scanf("%d%d",&n,&m);
if(m>n)
{
m=n;
}
printf("%d\n",ans[n][m]);
}
}
不过刚才我说的有一个条件:n,m没有大到离谱
那要是大到离谱呢?(n=6653525,m=4784854,对P=19260817取模)
我们请出Lucas定理
Lucas是什么?
当p=质数时
(这里没有证明,因为我的证明基于费马小定理和二项式定理,而这些不在本篇的探讨范围)
这样,复杂度可以降到
不过,Lucas一般是要求逆元的
详见前面几期的Luogu日报(不过好像被和谐了)
可以看一下我的Lucas板子:
#include<iostream>
#include<cstdio>
using namespace std;
long long n,m,p,t,ny[100005];
void qny()//求逆元
{
ny[1]=1;
for(register int a=2;a<=p-1;a++)
{
ny[a]=(p-(p/a))*ny[p%a]%p;
}
}
int zhs(int q,int x)//求组合数
{
if(q==0)
{
return 1;
}
long long ltt=1;
for(register int a=1;a<=q;a++)
{
ltt*=ny[a];
ltt%=p;
}
for(register int a=1;a<=q;a++)
{
ltt*=(x-a+1);
ltt%=p;
}
return ltt;
}
long long lucas(int s,int t)//主体部分
{
if(t==0)
{
return 1;
}
else
{
return (lucas(s/p,t/p)*zhs(s%p,t%p))%p;
}
}
int main()
{
scanf("%d",&t);
p=19260817;
qny();
for(int i=1;i<=t;i++)
{
scanf("%d%d",&n,&m);
printf("%lld\n",lucas(m,n));
}
}
当p不等于质数时,也是有办法解的
那就是扩展Lucas
不过那是基于CRT(中国剩余定理)的
本人不做深入研究
有兴趣的大佬可自行百度