luogu P1641 生成字符串
Description
lxhgww 需要把 n n n 个1 和 m m m 个0组成字符串,同时保证在任意的前 k k k 个字符中,1的个数不能少于0的个数。现在 lxhgww 想要知道满足要求的字符串共有多少个。
Solution
根据题意,字符串的长度为 n + m n+m n+m,如果将问题退化成只求要求有 n n n 个1的方案,答案很明显就是 C n + m n \mathrm{C}_{n+m}^n Cn+mn,但如果要保证在任意的前k个字符中,1的个数不能少于0的个数,就需要把不符合条件的去掉。
我们可以假设在前 2 k + 1 2k+1 2k+1 个 字符中有 k k k 个 1 1 1, k + 1 k+1 k+1个 0 0 0,此时,后 n + m − 2 k − 1 n+m-2k-1 n+m−2k−1 个字符中必然有 n − k n-k n−k 个 1 1 1, m − k − 1 m-k-1 m−k−1 个 0 0 0,将后面的 0 0 0和 1 1 1取反,则此时变成了一个有 n + 1 n+1 n+1 个 1 1 1和 m − 1 m-1 m−1 个 0 0 0的字符串,因此有 C n + 1 n \mathrm{C}_{n+1}^n Cn+1n 种情况是不符合条件的。
之所以要取反,原因很简单,可以将字符串看作是一个 n + m n+m n+m 位的二进制数,应当可以看出,每一个前 2 k + 1 2k+1 2k+1 位中有 k k k 个 1 1 1, k + 1 k+1 k+1 个 0 0 0的二进制数都唯一对应一个有 n + 1 n+1 n+1 个 1 1 1和 m − 1 m-1 m−1个 0 0 0的二进制数,可以得到不符合条件的总数量就是 C n + 1 n \mathrm{C}_{n+1}^n Cn+1n。
所以答案就应该是 C n + m n − C n + 1 n \mathrm{C}_{n+m}^n-\mathrm{C}_{n+1}^n Cn+mn−Cn+1n。
但是由于题目中 n n n, m m m 范围较大,做除法时需要用逆元取模求组合数。
设组合数(不化简)中的分母为 d d d,分母为 m m m,因为 n , m < p n,m<p n,m<p,所以可以知道 gcd ( d , p ) = 1 \gcd(d,p)=1 gcd(d,p)=1,且 p p p 为素数,可以由费马小定理得出: ( m / d ) % p = m % p ∗ p o w ( d , p − 2 ) % p (m/d)\%p=m\%p * pow(d,p-2)\%p (m/d)%p=m%p∗pow(d,p−2)%p。
最后再用快速幂处理一下即可。
#include<iostream>
#include<vector>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod = 20100403;
ll n, m;
ll quickpow(ll a, ll b)
{
ll ans = 1;
while (b > 0)
{
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll factorial(ll s)
{
ll fs = 1;
for (ll i = 2; i <= s; i++)
fs = fs * i % mod;
return fs;
}
ll c(ll x, ll y)
{
ll fx = factorial(x), fy = factorial(y), fxy = factorial(x - y);
fy = (fy * fxy) % mod;
ll d = quickpow(fy, mod - 2);
fx = (fx * d) % mod;
return fx;
}
int main()
{
scanf("%lld %lld", &n, &m);
printf("%lld\n", (c(n + m, n) - c(n + m, n + 1) + mod) % mod);
return 0;
}
考虑到每次计算组合数都要调用 f a c t o r i a l factorial factorial函数求组合数,可以对组合数进行预处理,减少时间的开销。
Code
#define _CRT_SECURE_NO_WARNINGS //VS上一般不支持直接使用scanf输入,需要加入该语句
#include<iostream>
#include<vector>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod = 20100403;
const int N = 2e6 + 10; //为了防止栈溢出,数组范围一般开数据范围的二倍
ll n, m;
ll fac[N];
void f() //预处理组合数
{
fac[1] = 1;
fac[0] = 1;
for (int i = 2; i <= N; i++)
fac[i] = fac[i - 1] * i % mod;
}
ll quickpow(ll a, ll b)
{
ll ans = 1;
while (b > 0)
{
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll c(ll x, ll y)
{
ll fx = fac[x], fy = fac[y], fxy = fac[x - y];
fy = (fy * fxy) % mod;
ll d = quickpow(fy, mod - 2); //利用费马小定理求组合数
fx = (fx * d) % mod;
return fx;
}
int main()
{
f();
scanf("%lld %lld", &n, &m);
printf("%lld\n", (c(n + m, n) - c(n + m, n + 1) + mod) % mod);
return 0;
}
将两种程序提交后比较一下
然而实际上时间并没有减少太多
通过分析其实也能看出来,其实factorial函数被调用的次数并不多,完全可以被忽略掉 (计算机:我超快的)
不过对于某些毒瘤题目来说,预处理组合数还是非常有用的
具体题目找不到惹,以后再说吧