zoukankan      html  css  js  c++  java
  • 牛客网NOIP赛前集训营提高组(第五场)串串 Solution

    题目描述

    给定非负整数a,b,c,d,求有多少对01串(S,T),满足以下条件:
    - S 由 a 个 0 , b 个 1 组成
    - T 由 c 个 0 , d 个 1 组成
    - T 可以由 S 删掉一些字符得到
    由于答案可能过大,你只需要输出答案对 1000000007 取模后的值

    这道题一开始没想着订正,后来偶然翻到这道题的题解,觉得这道题挺有订正的价值,所以就去学习了一下相关的思想方法,然后顺手把这题做掉了,那么现在我来讲一下这道题的做法:
    首先显然我们有一种思路,即先将 \(c\)\(0\)\(d\)\(1\) 组成 \(T\),然后再计算将 \(a-c\)\(0\)\(b-d\)\(1\) 插入 \(T\) 中构成 \(S\) 的方案数。那么这一步的方案数即为 \(C(c+d,c)\),可以理解为可重复元素的全排列,即 \((c+d)!/c!/d!\),也可以理解为给你 \(c+d\) 个零,然后从中选择 \(c\) 个将其变为 \(0\) 的方案数
    接下来我们需要考虑的是如何计算将 \(a-c\)\(0\)\(b-d\)\(1\) 插入 \(T\) 中构成 \(S\) 的方案数。如果我们直接无脑插入,例如现在的 \(T\) 串为 \(0000\),你要插入一个 \(0\),那么你会将 \(0\) 插入到任何一个空隙中,导致将 \(00000\) 这个 \(S\) 串多计算了 \(4\) 遍,所以按照出题人的说法:这种题目发现计算会重复的时候,就要制定一点限制来使得计算不重复、不遗漏,所以考虑如何制定
    (按题解里所说)经过思考可以想出,我们只要计算 \(T\)\(S\) 中最靠前的那一次出现即可。显然,在这样一种要求下,若我们把一个 \(0\) 插入到 \(T\) 中的一个 \(0\) 前面,那么必然这不是 \(T\)\(S\) 中最靠前的一次出现(自己画一画就知道了),而如果放到末尾,那不管怎么放 \(T\) 都是在 \(S\) 中最靠前的出现,\(1\) 也同理,所以 \(1\) 必须要放在 \(0\) 前面,\(0\) 必须要放在 \(1\) 前面。然后我们就可以枚举放在最后的 \(0\)\(1\) 个数,用插板法统计答案即可
    再来详细讲讲怎么用插板法统计答案。首先我们假设在末尾放了 \(Zero\)\(0\)\(One\)\(1\),那么同 \(T\) 可重复元素的全排列可知,这一步的方案数是 \(C(Zero+One,Zero)\),那么还剩 \(a-c-Zero\)\(0\) 要放在 \(d\)\(1\) 的前面,\(b-d-One\)\(1\) 要放在 \(c\)\(0\) 的前面。那么以 \(0\) 的插入为例,这就相当于我们有 \(d\) 个位置,要求把 \(a-c-Zero\) 个小球放入这 \(d\) 个位置,每个位置可以不放,也可以放多个。依照插板法的思路,这等价于我们有 \(d-1\) 块板要插入 \(a-c-Zero\) 个小球组成的空隙中。我们把小球与板看做同一种元素,那么一共就有 \(a-c-Zero+d-1\) 个元素,我们每次在里面选择 \(d-1\) 个元素将其变做板,那么就是我们所要求的方案数了,即 \(C(a-c-Zero+d-1,d-1)\),同理,\(1\) 的方案数为 \(C(b-d-One+c-1,c-1)\),由乘法原理,得当有 \(Zero\)\(0\)\(One\)\(1\) 在末尾时,方案数为 \(C(Zero+One,Zero)*C(a-c-Zero+d-1,d-1)*C(b-d-One+c-d,c-1)\),累加完成后再乘上 \(T\) 串的排列方式 \(C(c+d,c)\) 即是我们所要的答案
    哦还有当 \(c\)\(0\)\(d\)\(0\) 时还要特别考虑一下,我就先不多说了,代码如下:
    #include<cstdio>
    #include<iostream>
    using namespace std;
    typedef long long ll;
    const int mod=1e9+7;
    const int N=4e3+10;
    int a,b,c,d,fac[N],inv[N];
    ll ans;
    inline int C(int n,int m){if(n<m||m<0)return 0;return (ll)fac[n]*inv[m]%mod*inv[n-m]%mod;}
    int main(){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        fac[0]=inv[0]=inv[1]=1;
        for(register int i=1;i<=N-10;i++)fac[i]=((ll)fac[i-1]*i)%mod;
        for(register int i=2;i<=N-10;i++)inv[i]=(-1ll*mod/i*inv[mod%i]%mod+mod)%mod;
        for(register int i=2;i<=N-10;i++)inv[i]=(1ll*inv[i-1]*inv[i])%mod;
        if(!c||!d){printf("%d\n",C(a+b,a));return 0;}
        a-=c;b-=d;
        for(register int Zero=0;Zero<=a;Zero++)
            for(register int One=0;One<=b;One++)
                ans=(ans+1ll*C(Zero+One,Zero)*C(a-Zero+d-1,d-1)%mod*C(b-One+c-1,c-1)%mod)%mod;
        printf("%lld\n",1ll*ans*C(c+d,c)%mod);
        return 0;
    }
    
  • 相关阅读:
    宏任务、微任务与Event Loop
    puppteer的使用
    docker的使用 -- windows
    vscode集成eslint
    删除git中无用的大文件
    git 使用
    利用chrome devtool 观察页面占用内存
    JS对象-不可扩展对象、密封对象、冻结对象
    数学
    素数 + 背包
  • 原文地址:https://www.cnblogs.com/ForwardFuture/p/9868515.html
Copyright © 2011-2022 走看看