题意
某个机器在第
i
i
i 个小时可以生产至多
f
i
f_i
fi 个黄色气球和
g
i
g_i
gi 个蓝色气球,也可以不生产任何一个气球
给定接下来
n
n
n 个小时的生产指数
f
i
,
g
i
f_i, g_i
fi,gi,每个小时内生产的气球顺序可任意确定,但是前一小时生产的气球都排在后一小时生产的气球之前
计算有多少种不同的生产序列,并对 1 0 9 + 7 10^9 + 7 109+7 取模(空集也算)
1 ≤ n ≤ 1 0 5 , 0 ≤ f i , g i ≤ 1 0 6 1 \leq n \leq 10^5, \; 0 \leq f_i, g_i \leq 10^6 1≤n≤105,0≤fi,gi≤106
思路
我们先来考虑如何检测一个序列是否合法,可以贪心地检测,即对于当前的气球,如果当前小时的这一种类的气球还有生产余量,就在这一小时生产,否则跳到下一小时。如果在 n n n 个小时内可以匹配完这个序列,那么该序列合法。
进一步观察可以发现: 这个匹配的过程和合法的序列是一一对应的关系,所以我们可以直接对匹配的过程
d
p
dp
dp
记
d
p
i
,
j
dp_{i, j}
dpi,j 表示第
i
i
i 个小时生产的第一种气球颜色必须为
j
(
j
∈
{
0
,
1
}
)
j (j \in \{0, 1\})
j(j∈{0,1}),对应多少种合法方案,那么我们可以枚举另一种气球在这一小时内生产的数量,进行转移
假设当前小时内生产的第一个气球种类
j
j
j 最多在这一小时内生产
x
x
x 个
(
x
>
0
)
(x > 0)
(x>0),我们先将
x
−
1
x - 1
x−1,因为要去掉固定为开头的那个,那么后面我们方案数为:
∑
i
=
0
y
(
x
+
i
i
)
=
(
x
+
i
+
1
x
+
1
)
\sum_{i = 0}^{y} \binom{x + i}{i} = \binom{x + i + 1}{x + 1}
i=0∑y(ix+i)=(x+1x+i+1)
转移到
d
p
i
+
1
,
j
dp_{i + 1, j}
dpi+1,j 即可
如果决定匹配过程在当前小时结束,那么方案数为:
∑
i
=
0
x
∑
j
=
0
y
(
i
+
j
i
)
=
(
x
+
2
)
(
x
+
y
+
2
x
+
2
)
−
y
−
1
y
+
1
\sum_{i = 0}^{x} \sum_{j = 0}^{y} \binom{i + j}{i} = \frac{(x + 2)\binom{x + y + 2}{x + 2} - y - 1}{y + 1}
i=0∑xj=0∑y(ii+j)=y+1(x+2)(x+2x+y+2)−y−1
至于这个公式这么推出来的,笔者也不懂…
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
template<class T>
constexpr T power(T a, ll b){
T res = 1;
while(b){
if(b&1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
ll res = a * b - ll(1.L * a * b / mod) * mod;
res %= mod;
if(res < 0) res += mod; //误差
return res;
}
template<ll P>
struct MLL{
ll x;
constexpr MLL() = default;
constexpr MLL(ll x) : x(norm(x % getMod())) {}
static ll Mod;
constexpr static ll getMod(){
if(P > 0) return P;
return Mod;
}
constexpr static void setMod(int _Mod){
Mod = _Mod;
}
constexpr ll norm(ll x) const{
if(x < 0){
x += getMod();
}
if(x >= getMod()){
x -= getMod();
}
return x;
}
constexpr ll val() const{
return x;
}
explicit constexpr operator ll() const{
return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
}
constexpr MLL operator -() const{ //负号,等价于加上Mod
MLL res;
res.x = norm(getMod() - x);
return res;
}
constexpr MLL inv() const{
assert(x != 0);
return power(*this, getMod() - 2); //用费马小定理求逆
}
constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
return *this;
}
constexpr MLL& operator += (MLL rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MLL& operator -= (MLL rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MLL& operator /= (MLL rhs) & {
return *this *= rhs.inv();
}
friend constexpr MLL operator * (MLL lhs, MLL rhs){
MLL res = lhs;
res *= rhs;
return res;
}
friend constexpr MLL operator + (MLL lhs, MLL rhs){
MLL res = lhs;
res += rhs;
return res;
}
friend constexpr MLL operator - (MLL lhs, MLL rhs){
MLL res = lhs;
res -= rhs;
return res;
}
friend constexpr MLL operator / (MLL lhs, MLL rhs){
MLL res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
ll v;
is >> v;
a = MLL(v);
return is;
}
friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
return os << a.val();
}
friend constexpr bool operator == (MLL lhs, MLL rhs){
return lhs.val() == rhs.val();
}
friend constexpr bool operator != (MLL lhs, MLL rhs){
return lhs.val() != rhs.val();
}
};
const ll mod = 1e9 + 7;
using Z = MLL<mod>;
template<>
ll MLL<0ll>::Mod = 998244353;
struct Comb {
int n;
std::vector<Z> _fac;
std::vector<Z> _invfac;
std::vector<Z> _inv;
Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
Comb(int n) : Comb() {
init(n);
}
void init(int m) {
m = std::min(1ll * m, Z::getMod() - 1);
if (m <= n) return; //已经处理完了需要的长度
_fac.resize(m + 1);
_invfac.resize(m + 1);
_inv.resize(m + 1);
for (int i = n + 1; i <= m; i++) {
_fac[i] = _fac[i - 1] * i;
}
_invfac[m] = _fac[m].inv();
for (int i = m; i > n; i--) { //线性递推逆元和阶乘逆元
_invfac[i - 1] = _invfac[i] * i;
_inv[i] = _invfac[i] * _fac[i - 1];
}
n = m; //新的长度
}
Z fac(int m) {
if (m > n) init(2 * m);
return _fac[m];
}
Z invfac(int m) {
if (m > n) init(2 * m);
return _invfac[m];
}
Z inv(int m) {
if (m > n) init(2 * m);
return _inv[m];
}
Z binom(int n, int m) { //二项式系数
if (n < m || m < 0) return 0;
return fac(n) * invfac(m) * invfac(n - m);
}
} comb;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin >> t;
while(t--){
int n;
std::cin >> n;
std::vector<std::array<int, 2>> a(n + 1);
fore(i, 1, n + 1) std::cin >> a[i][0] >> a[i][1];
std::vector<std::array<Z, 2>> dp(n + 5);
Z ans = 1; //空序列的方案
dp[1][0] = dp[1][1] = 1;
fore(i, 1, n + 1){
fore(j, 0, 2){
auto b = a[i];
if(!b[j]){
dp[i + 1][j] += dp[i][j];
continue;
}
--b[j];
Z way = (b[0] + 2) * comb.binom(b[0] + b[1] + 2, b[0] + 2) - b[1] - 1;
way *= comb.inv(b[1] + 1);
ans += way * dp[i][j];
fore(k, 0, 2){
dp[i + 1][k] += comb.binom(b[0] + b[1] + 1, b[k] + 1) * dp[i][j];
}
}
}
std::cout << ans << endl;
}
return 0;
}