排列组合与计数(各位大佬帮忙查错,谢谢)

生活中我们总是少不了组合数问题

今天我就来讲一讲有关组合数的问题

首先看一看全排列问题

先看一个栗子:

我给你3个颜色不同的球,让你算出一共有多少种排列

我们画一下:

如图所示,答案一共6种

那么这个6又是怎么来的呢?

我们来看一下下面这个过程:

假设第一步我们选了红色

那么第二步,我们就只剩下蓝色和黄色可选。

第二步如果我选了蓝色,那么第三步我就只能选黄色。

反之,如果我选了黄色,第三步我就只能选蓝色(莫名像递归)

合起来,在第一次选红色的情况下,我们一共可以得到两个不同的排列

当然,第一次也可以选蓝色或者是黄色,所以答案有2+2+2=2*3=6种

用一句话来总结,就是:

如果有n种不同颜色的小球,那么

第一次选择有n种,第二次选择有n-1种......最后一次选择有1种可能。

所以一共有n*(n-1)*(n-2)*……*1种方案

用一棵树表示上面的栗子(n=3)就是这样:

由此,我们可以得到全排列公式:

Pn(P表示全排列)=n*(n-1)*(n-2)*……*1=n!;

组合去重:

这不是太简单了???大佬们嘲笑本蒟蒻

好,那么我增加一下难度

这回我的球数变到4个

还是三种颜色,就是红球变成了两个

这回又是怎样呢?

我们画一下

(这里请将绿色看做第二个红色)

但是这两种其实是一个方案

 所以,总方案数要除以2

那么如果有三个红球呢?

答案就是:所有球全排列数/红色球的全排列数

根据这个思想,当有多个颜色相同的球时,排列数=:

P(总球数)/(P(第1种颜色球的数量)*P(第2种颜色球的数量)*……*P(第n种颜色球的数量))

这就是组合去重

选取方案数:(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)

所以我们要去重

所以Cnm=n*(n-1)*……(n-m+1)/Pm

好啦,轻松一下,我们玩个游戏

看下面这个三角形:

你能发现什么呢?

大多数人都能发现,第n行第m个数=第n-1行第m-1个数+第n-1行第m个数(v[n][m]=v[n-1][m-1]+v[n-1][m])

下面我们把图改一下,标上编号:

我们会发现v[n][m]居然=Cnm!

那么组合数是不是具有递推性呢?

我们来证明一下:

得证。(看不懂的请跳过,背公式就行)

那么这有用什么用呢?

想想看,如果你要求一个区间内的所有组合数,那么你按公式的话,每次要算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
",ans[n][m]);
    }
}

不过刚才我说的有一个条件:n,m没有大到离谱

 那要是大到离谱呢?(n=6653525,m=4784854,对P=19260817取模)

我们请出Lucas定理

Lucas是什么?

当p=质数时

Cnm(modp)=C(n%p)(m%p)*C(n/p)(m/p)

(这里没有证明,因为我的证明基于费马小定理和二项式定理,而这些不在本篇的探讨范围)

这样,复杂度可以降到logpn,这样就可以过啦

不过,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
",lucas(m,n));
    }
}

当p!=质数时,也是有办法解的

那就是扩展Lucas

不过那是基于CRT(中国剩余定理)的

本人不做深入研究

有兴趣的大佬可自行百度

原文地址:https://www.cnblogs.com/ztz11/p/9519167.html