组合数学-排列组合整理

来源:互联网 发布:基于用户协同过滤算法 编辑:程序博客网 时间:2024/06/08 04:43

此文是我整理组合数学排列组合知识的博文,排列组合从零开始。。。加油!


1.重复组合:

从n种不同元素中取出m的元素(方法是从n个元素中每次取出一个后,放回,再取另外一个,直到取出m个元素),每一种元素不超过m个,且每一种类的个数要大于等于m,并成一组,叫做n个不同元素的一个m-可重组合。n个不同元素的m-可重组合数为C上标m下标n+m-1,m可以是任意的正整数。
也可以逆向来看,将m个元素(只计较个数)散落到n个不同的地点,允许某地落下多个或0个。相当于将m元的链条分断为n部分,只需将(n-1)个断点与m个元素重新形成m+n-1元的链条,每一个不同的链条都对应一种分割,故共有组合数为C上标n-1下标n+m-1。

解释


实际上大家还应该注意到一点,就是有重复组合不考虑取出的元素的顺序,通俗来说,你第一次取出一号元素第二次取出三号元素和你第一次取出三号元素第二次取出一号元素是一样的情况;有了这点说明后可以进行证明了。可以把该过程看作是一个“放球模型”;n个不同的元素看作是n个格子,去掉头尾之后中间一共有(n-1)块相同的隔板;用m个相同的小球代表取m次;则原问题可以简化为将m个不加区别的小球放进n个格子里面,问有多少种放法;注意到格子的头尾两块隔板无论什么情况下位置都是不变的,故去掉不用考虑;相当于m个相同的小球和(n-1)块相同的隔板先进行全排列:一共有(m+n-1)!种排法,再由于m个小球和(n-1)块隔板是分别不加以区分的,所以除以重复的情况:m!*(n-1)!;于是答案就是:(m+n-1)!/(m!*(n-1)!)=C(m,n+m-1)。

其它

当每一种类的个数小于m时,就要减去这个种类中在上述组合中的多余的为空个数。

HDU 5894 hannnnah_j’s Biological Test (组合数学)


题目大意:有n张板凳围成一圈,有m个人,要让m个人都坐到凳子上且任意两人之间相隔>=k 个凳子,问有多少种方法%(1e9+7)

题目思路:组合数学

     我们这样考虑,既然每个人相距>=k 个凳子,m个人就至少有m*k个凳子不能坐人,那我们先从中抽出这m*k个凳子,其它

     凳子都可以坐了,然后我们考虑第一个人坐到了一个位置上,剩下的人就有C(n-m*k-1,m-1)种坐法,而第一个人有n种

     初始选择,但由于m个人又相同,故应该是C(n-m*k-1,m-1)*n/m种坐法。

总结:我们可以这样想, 先把m个人安排好, 每个人之间有k个座位,那样剩下的随意插到两个人之间就好了, 所以是 剩下n-m*k-m凳子, 插到m个空里,可以有空,问有几种分法,相当于将m个相同小球放到n个不同的位置,故共有组合数为C上标n-1下标n+m-1;对于环来说, 确定了一个起点,这个环就唯一确定了, 可以唯一被拉成一条链,而每个凳子,我都可以做这个环的起点,又因为m个人是一样的, 所以m个人当作起点的时候,情况重复了m次,这里在一个环里放1一个,放在哪都是一样,无所谓的, 所以直接可以C(n-m*k-1,m-1),下一题就不一样了

#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>using namespace std;typedef long long ll;const int maxn = 1e6 + 7;const int Mod = 1e9 + 7;ll inv[maxn], cnt[maxn], f2[maxn];void init(){    cnt[0] = cnt[1] = 1;    inv[0] = inv[1] = 1;    f2[0] = f2[1] = 1;    for(int i = 2; i < maxn; i++)    {        cnt[i] = (cnt[i-1]*i)%Mod;  //阶乘        inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;  //线性求逆元        f2[i] = f2[i - 1] * inv[i] % Mod;    }}ll C(ll x, ll y) //C(x, y){    if(x < y || y < 0) return 0;//    return (cnt[x]*inv[y])%Mod*inv[x-y]%Mod;    return cnt[x] * f2[y] % Mod * f2[x - y] % Mod;  //线性求组合数}int main(){    int T;    ll n, m, k;    init();    cin >> T;    while(T--)    {        scanf("%lld%lld%lld", &n, &m, &k);        if(m == 1)        {            printf("%lld\n", n);            continue;        }        printf("%lld\n", n*C(n-m*k-1, m-1)%Mod*inv[m]%Mod);    }    return 0;}

 

hihoCoder 1075 开锁魔法III DP+组合数


描述

一日,崔克茜来到小马镇表演魔法。

其中有一个节目是开锁咒:舞台上有 n 个盒子,每个盒子中有一把钥匙,对于每个盒子而言有且仅有一把钥匙能打开它。初始时,崔克茜将会随机地选择 k 个盒子用魔法将它们打开。崔克茜想知道最后所有盒子都被打开的概率,你能帮助她回答这个问题吗?

输入

第一行一个整数 T (T ≤ 100)表示数据组数。 对于每组数据,第一行有两个整数 n 和 k (1 ≤ n ≤ 300, 0 ≤ k ≤ n)。 第二行有 n 个整数 ai,表示第 i 个盒子中,装有可以打开第 ai 个盒子的钥匙。

输出

对于每组询问,输出一行表示对应的答案。要求相对误差不超过四位小数。

样例输入
45 12 5 4 3 15 22 5 4 3 15 32 5 4 3 15 42 5 4 3 1
样例输出
0.0000000000.6000000000.9000000001.000000000

思路:首先可以很明显的知道,总方案数是C(n,k), 这个可以变成cnt个环, 如果开k次, 每个环里各有一次,那就可以。这题跟上题有点像? 是不是可以每个环我都开一次, 那么其余就是重复组合问题了, C(m-1, m+cnt-1)就好了?但是这样是错误的。。首先每个盒子是不一样的, 你开不同盒子方案是不一样,那是不是每个环第一次开有num[i](每个环的元素个数)中选择?那我把第一次所有可能都乘起来就好? 答案是不对的, 那你剩下的k-cnt的钥匙,重复组合的放到n-cnt里,对于每个放到环里的钥匙,他不是开任意一个都是一样的。。因为每个箱子不同, 上面那题放凳子, 你在一个空里放k个,他放在哪都是无所谓的。。。这题应该,比如我第i个环我要放x个, 那么他的方案数是 C(num[i], x),从num【i】里选x个开开,而不是重复组合,随意分给每个环几个, 一旦确定分几个,他环里面还是要组合数的,所以这题不能重复组合,或者这样理解, 重复组合是n个无区别的小球,放到m个有区别的盒子,这里的k次魔法应该理解成不相同的,每个环里的盒子不同,他开哪个都不同,正解是dp+组合数,也很好理解

还有一种想法:C(n-m,k-m)*(πnum[i]//代表num从1乘到n)/C(n,k),m为环的个数,我一定要将每个环都选一个点才行,那么就剩下了n-m个点,在其中选k-m个点的方案数就是所有可行解的方案数,这样会有很多重复的。。dp的好处就是去掉了重复的情况。。。dp[i][j] += dp[i-1][j-cur]*C[num[i]][cur];  dp[i][j]代表前i个用j个魔法,cur枚举第i个用的魔法数量,方程很显而易见,不会有重复。

正解:由于我们可以通过魔法打开一个箱子,随后用其中的钥匙打开下一个箱子,然后再次进行相同动作,直到再次得到最初用魔法打开的箱子的钥匙。我们把这几个箱子看作一组、称作一个“循环”。那么n个箱子会被我们分作sum个组,每组里有多少个箱子我们存储在part里,为了供下一步的dp推算使用。

我们用一个二维dp数组,dp[i][j]表示用了j次魔法,能够解决i个分组的概率。其中dp[0][0] = 1.0,对于每个分组,我们可能使用几次魔法,也需要进行一次组合数来计算。最终,我们将dp[sum][n]除以总可能数C(n, k),即得到最终答案。

#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>using namespace std;const int maxn = 3e2 + 7;double C[maxn][maxn], dp[maxn][maxn];int a[maxn], num[maxn], book[maxn];void init(){    for(int i = 0; i < maxn; i++)    {        C[i][0] = C[i][i] = 1;        for(int j = 1; j < i; j++)        {            C[i][j] = C[i-1][j-1] + C[i-1][j];        }    }}int main(){    int T, n, k;    cin >> T;    init();    while(T--)    {        memset(book, 0, sizeof(book));        memset(dp, 0, sizeof(dp));        int tot = 0;        scanf("%d%d", &n, &k);        for(int i = 1; i <= n; i++)            scanf("%d", &a[i]);        for(int i = 1; i <= n; i++)        {            if(book[i]) continue;            int index = a[i], cnt = 1;            while(index != i)            {                book[index] = 1;                index = a[index];                cnt++;            }            if(cnt) num[++tot] = cnt;        }        dp[0][0] = 1;        for(int i = 1; i <= tot; i++)        {            for(int cur = 1; cur <= num[i]; cur++)            {                for(int j = cur; j <= k; j++)                    dp[i][j] += dp[i-1][j-cur]*C[num[i]][cur];            }        }        printf("%.9f\n", dp[tot][k]/C[n][k]);    }    return 0;}





1 0
原创粉丝点击