组合计数问题

组合计数问题


组合计数在竞赛中是真的难,特别是和容斥结合在一起时,真就难得起飞。

学习组合计数可以先看一下B站大佬视频讲解常见的几种基础组合计数问题

视频



P1287 盒子与球


题目描述

现有 r个互不相同的盒子和 n 个互不相同的球,要将这 n 个球放入 r个盒子中,且不允许有空盒子。请求出有多少种不同的放法。

两种放法不同当且仅当存在一个球使得该球在两种放法中放入了不同的盒子。


思路

可以采用 d p dp dp来解决本题。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 i i i个球 j j j个盒子时的方案数,那状态转移方程又是什么呢?

当放第 i i i个球时的状态肯定是要通过第 i − 1 i-1 i1个球转移过来的,因为要放 j j j个盒子,那么在放 i − 1 i-1 i1个时可能就放了 j − 1 j-1 j1或者 j j j个了,那此时 d p [ i ] [ j ] dp[i][j] dp[i][j]就应该由 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1] d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移过来,但是在由 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移的时候因为第 i i i个球可以在 j j j个盒子中任意放置,则此时应该加上 d p [ i − 1 ] [ j ] × j dp[i-1][j]\times j dp[i1][j]×j,而由 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]转移时第 i i i个球只能放在第 j j j个盒子中,所以不需要额外操作。分析到这里状态转移方程就出来了:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] × j + d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j]\times j+dp[i-1][j-1] dp[i][j]=dp[i1][j]×j+dp[i1][j1]
所以答案就是 d p [ n ] [ m ] dp[n][m] dp[n][m]?当然不是啦,应该在每个盒子放入小球时,应该对盒子进行一次全排列,所以最后答案应该是 d p [ n ] [ m ] × A m m dp[n][m]\times A_m^m dp[n][m]×Amm


参考代码
#include<bits/stdc++.h>
using namespace std;
int dp[15][15];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=min(m,i);j++)
        dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1];
    }
    int ans=dp[n][m];
    for(int i=1;i<=m;i++)
    ans*=i;
    printf("%d\n",ans);
    system("pause");
    return 0;
}



P4071 [SDOI2016]排列计数


题目描述

求有多少种 1 到 n的排列 a,满足序列恰好有 m个位置 i i i​,使得 a i = i a_i=i ai=i​。

答案对 1 0 9 + 7 10^9+7 109+7取模。


思路

本题是错位排序的一种应用,在上述B站链接里有讲错位排序。什么是错位排序呢?错位排序

错位排序就如同全排列中对任意的 a [ i ] a[i] a[i] a [ i ] ≠ i a[i]\ne i a[i]=i​​​的方案数​,对与长度为i的排列中,错位排序排列的数量为:

i = 1 i=1 i=1时, d [ i ] = 0 d[i]=0 d[i]=0

i = 2 i=2 i=2​时, d [ i ] = 1 d[i]=1 d[i]=1

i > 2 i>2 i>2​时, d [ i ] = ( i − 1 ) × ( d [ i − 1 ] + d [ i − 2 ] ) d[i]=(i-1)\times (d[i-1]+d[i-2]) d[i]=(i1)×(d[i1]+d[i2])​​

所以这题就是有了这个就变得很简单了啦,当 n = m n=m n=m​​时肯定答案就是1啦,其余的时候不就是 C n m × d [ n − m ] C_n^m \times d[n-m] Cnm×d[nm]​​​​


参考代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6+10;
const ll M=1e9+7;
ll dp[maxn],fac[maxn];
void init()
{
    dp[1]=0,dp[2]=1;
    for(int i=3;i<maxn;i++)
    dp[i]=(i-1)*((dp[i-1]+dp[i-2])%M)%M;
    fac[0]=1;
    for(int i=1;i<maxn;i++)
    fac[i]=fac[i-1]*i%M;
}
ll ksm(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y&1)ans=ans*x%M;
        x=x*x%M;
        y>>=1;
    }
    return ans;
}
ll inv(ll z)
{
    return ksm(z,M-2);
}
ll C(ll n,ll m)
{
    if(m>n)return 0;
    return fac[n]*inv(fac[m])%M*inv(fac[n-m])%M;
}
int main()
{
    int t;
    scanf("%d",&t);
    init();
    while(t--)
    {
        ll n,m;
        scanf("%lld%lld",&n,&m);
        if(n==m)printf("1\n");
        else
        {
            ll ans=dp[n-m];
            ans=ans*C(n,m)%M;
            printf("%lld\n",ans);
        }
    }
    system("pause");
    return 0;
}



P1450 [HAOI2008]硬币购物


题目描述

共有 4种硬币。面值分别为$ c_1 ​ , ​, ,c_2 , , ,c_3 , , ,c_4$。

某人去商店买东西,去了 n 次,对于每次购买,他带了 d i d_i di 枚 i种硬币,想购买 s的价值的东西。请问每次有多少种付款方法。


思路

拿到这题第一思路多重背包,但是q次询问,直接可以TLE起飞,可能明年都跑不完。那怎么些勒,可以通过总方案数-不符合条件的方案数来进行求解。

可以先通过完全背包将每一种硬币没有限制时的所有方案数 d p [ i ] dp[i] dp[i]​求出来,然后通过求出不符合条件数进行求解。

对于不满住要求的方案数,我们可以先来看第一种硬币不符合要求时的情况,我们可以先强制消费 d [ 1 ] + 1 d[1]+1 d[1]+1​个硬币,那么后面的硬币无论如何选择都已经不符合条件了,那后面还需支付的钱为 s − c [ 1 ] × ( d [ 1 ] + 1 ) s-c[1]\times (d[1]+1) sc[1]×(d[1]+1)​。所有可以得到第一种硬币超过要求的方案数=支付 s − c [ 1 ] × ( d [ 1 ] + 1 ) s-c[1]\times (d[1]+1) sc[1]×(d[1]+1)​的方案数。​

那么第一种硬币超过要求的方案数= d p [ s − c [ 1 ] × ( d [ 1 ] + 1 ) ] dp[ s-c[1]\times (d[1]+1)] dp[sc[1]×(d[1]+1)]

那第二种、三种、、、的不符合要求的方案数都可以求出来。但是难道只需要减去这些不符合条件的方案数都减去就行了吗?当然不是了,在你求第一种硬币不符合要求时的时候没有保证第二种、第三种、第四种硬币都符合要求,所有单纯减去肯定是不行的。令A为第一种硬币不符合条件方法的集合,B为第二种硬币不符合条件方法的集合,C为第三种硬币不符合条件方法的集合,D为第四种硬币不符合条件方法的集合

对于第一种和第二种硬币, d p [ s ] dp[s] dp[s]​​​应该减去的是 A ⋃ B A\bigcup B AB​​,即 A + B − A ⋂ B A+B-A\bigcap B A+BAB​​​

所以对四种硬币最后答案应该是
d p [ s ] − ( A ⋃ B ⋃ C ⋃ D ) = d p [ s ] − A − B − C − D + A ⋂ B + A ⋂ C + A ⋂ D + B ⋂ C + B ⋂ D + C ⋂ D − A ⋂ B ⋂ C − A ⋂ B ⋂ D − A ⋂ C ⋂ D − B ⋂ C ⋂ D + A ⋂ B ⋂ C ⋂ D dp[s]-(A\bigcup B \bigcup C \bigcup D) =dp[s]-A-B-C-D+A\bigcap B+A\bigcap C+A\bigcap D+B\bigcap C+B\bigcap D+C\bigcap D-A\bigcap B\bigcap C-A\bigcap B\bigcap D-A\bigcap C\bigcap D-B\bigcap C\bigcap D+A\bigcap B\bigcap C\bigcap D dp[s](ABCD)=dp[s]ABCD+AB+AC+AD+BC+BD+CDABCABDACDBCD+ABCD

参考代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int n,c[10],d[10];
ll dp[maxn];
int main()
{
    for(int i=1;i<=4;i++)
    scanf("%d",&c[i]);
    scanf("%d",&n);
    dp[0]=1;
    for(int i=1;i<=4;i++)
    {
        for(int j=c[i];j<maxn;j++)
        dp[j]+=dp[j-c[i]];
    }
    while(n--)
    {
        int s;
        for(int i=1;i<=4;i++)
        scanf("%d",&d[i]);
        scanf("%d",&s);
        ll ans=dp[s];
        for(int i=1;i<(1<<4);i++)
        {
            int cnt=0;
            int sum=0;
            for(int j=0;j<4;j++)
            {
                int x=(i>>j)&1;
                if(x==1)
                {
                    cnt++;
                    sum+=(d[j+1]+1)*c[j+1];
                }
            }
            if(s>=sum)
            {
                if(cnt%2==0)ans+=dp[s-sum];
                else ans-=dp[s-sum];
            }
            
        }
        printf("%lld\n",ans);
    }
    system("pause");
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值