小白月赛17 J-计数 组合计数
传送门: https://ac.nowcoder.com/acm/contest/1085/J
题意
有一个含有n个数字的序列,每个数的大小是不超过1000的正整数,同时这个序列是个单调不增序列。但是很不幸的是,序列在保存过程中有些数字丢失了,请你根据上述条件,计算出有多少种不同的序列满足上述条件,答案对1000000007取模。
思路
方法一:隔板法
假
如
连
续
一
段
有
n
个
0
,
所
以
需
要
在
这
n
个
位
置
中
插
入
数
,
而
插
入
的
数
为
[
1
,
m
]
(
大
家
应
该
能
看
出
来
)
。
假如连续一段有n个0,所以需要在这n个位置中插入数,而插入的数为[1,m](大家应该能看出来)。
假如连续一段有n个0,所以需要在这n个位置中插入数,而插入的数为[1,m](大家应该能看出来)。
所
以
我
们
假
设
[
1
,
m
]
中
1
出
现
a
1
次
,
m
出
现
a
m
次
。
即
i
出
现
a
i
次
。
所以我们假设[1,m] 中1出现a_1次,m出现a_m次。即i出现a_i次。
所以我们假设[1,m]中1出现a1次,m出现am次。即i出现ai次。
所
以
要
将
这
些
数
字
插
入
n
个
位
置
中
,
即
统
计
他
们
可
以
出
现
的
次
数
,
即
所以要将这些数字插入n个位置中,即统计他们可以出现的次数,即
所以要将这些数字插入n个位置中,即统计他们可以出现的次数,即
n
=
a
1
+
a
2
+
.
.
.
+
a
m
(
0
≤
a
i
≤
n
)
n=a_1+a_2+...+a_m(0\leq a_i \leq n)
n=a1+a2+...+am(0≤ai≤n)
等 价 于 n 个 小 球 放 进 m 个 盒 子 里 , 隔 板 法 即 可 , 由 于 a i 可 以 等 于 0 , 所 以 我 们 让 每 一 个 盒 子 都 是 事 先 放 进 1 个 小 球 , 即 n + m , 这 样 求 出 的 方 案 为 C n + m − 1 m − 1 , 每 一 种 方 案 为 一 种 序 列 。 等价于n个小球放进m个盒子里,隔板法即可,由于a_i可以等于0,所以我们让每一个盒子都是事先放进1个小球,即n+m,这样求出的方案为C_{n+m-1}^{m-1},每一种方案为一种序列。 等价于n个小球放进m个盒子里,隔板法即可,由于ai可以等于0,所以我们让每一个盒子都是事先放进1个小球,即n+m,这样求出的方案为Cn+m−1m−1,每一种方案为一种序列。
Code(159MS)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int N = 1e6 + 10;
ll fac[N];
ll inv[N];
ll quick_pow(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void init() {
fac[0] = 1;
for(int i = 1;i < N; i++) fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = quick_pow(fac[N - 1], mod - 2);
for(int i = N - 2;i >= 0; i--) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
ll C(int m, int n) {
ll ans = fac[m] * inv[n] % mod * inv[m - n] % mod;
return ans;
}
int main() {
init();
int n;
cin >> n;
int pre = 1000;
int cnt = 0;
ll ans = 1;
for(int i = 1;i <= n; i++) {
int x;
cin >> x;
if(!x) cnt++;
else {
int len = pre - x + 1;
ans = ans * C(cnt + len - 1, len - 1) % mod;
pre = x;
cnt = 0;
}
}
if(cnt) {
ans = ans * C(cnt + pre - 1, pre - 1) % mod;
}
cout << ans << endl;
}
方法二:单调不增变单调递减
将 单 调 不 增 序 列 变 为 点 掉 递 减 序 列 , 只 需 将 i 位 置 上 的 数 + ( n − i + 1 ) 即 可 。 将单调不增序列变为点掉递减序列,只需将i位置上的数+(n-i+1)即可。 将单调不增序列变为点掉递减序列,只需将i位置上的数+(n−i+1)即可。
对 于 一 段 连 续 为 0 的 区 间 , 就 不 需 要 考 虑 会 取 同 样 的 值 , 直 接 在 可 以 选 择 的 数 中 选 出 n 个 即 可 。 对于一段连续为0的区间,就不需要考虑会取同样的值,直接在可以选择的数中选出n个即可。 对于一段连续为0的区间,就不需要考虑会取同样的值,直接在可以选择的数中选出n个即可。
假 设 可 以 取 的 值 有 m 个 , 那 么 方 案 数 为 C m n 假设可以取的值有m个,那么方案数为C_{m}^n 假设可以取的值有m个,那么方案数为Cmn
注 意 0 和 n 位 置 也 要 处 理 。 注意0和n位置也要处理。 注意0和n位置也要处理。
Code(159MS)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int N = 1e6 + 10;
ll fac[N];
ll inv[N];
ll quick_pow(ll a, ll b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void init() {
fac[0] = 1;
for(int i = 1;i < N; i++) fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = quick_pow(fac[N - 1], mod - 2);
for(int i = N - 2;i >= 0; i--) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
ll C(int m, int n) {
ll ans = fac[m] * inv[n] % mod * inv[m - n] % mod;
return ans;
}
int a[N];
int main() {
init();
int n;
cin >> n;
n += 2;
ll ans = 1;
a[1] = 1000 + n; a[n] = 2;
for(int i = 2;i < n; i++) {
cin >> a[i];
if(a[i])
a[i] += n - i + 1;
}
int suf = 0;
for(int i = 1;i <= n; i++) {
if(a[i]) {
if(suf)
ans = ans * C(a[suf] - a[i] - 1, i - suf - 1) % mod;
suf = i;
}
}
cout << ans << endl;
}