牛客网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;
}