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


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

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

    首先看一看全排列问题

    先看一个栗子:

    我给你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(中国剩余定理)的

    本人不做深入研究

    有兴趣的大佬可自行百度

  • 相关阅读:
    SPOJ 4487. Can you answer these queries VI splay
    Oracle Enterprise Linux 64-bit 下Oracle11g的监听配置改动及測试步骤
    欧拉函数
    安装Windows7步骤
    在Eclipse中执行、配置Hadoop
    java设计模式演示样例
    VC中获取窗体句柄的各种方法
    HTML5 Canvas中实现绘制一个像素宽的细线
    Java实现 蓝桥杯VIP 基础练习 Sine之舞
    Java实现 蓝桥杯VIP 基础练习 Sine之舞
  • 原文地址:https://www.cnblogs.com/ztz11/p/9519167.html
Copyright © 2020-2023  润新知