小白月赛17 J-计数 组合计数


传送门: https://ac.nowcoder.com/acm/contest/1085/J

题意

有一个含有n个数字的序列,每个数的大小是不超过1000的正整数,同时这个序列是个单调不增序列。但是很不幸的是,序列在保存过程中有些数字丢失了,请你根据上述条件,计算出有多少种不同的序列满足上述条件,答案对1000000007取模。

思路

方法一:隔板法

假 如 连 续 一 段 有 n 个 0 , 所 以 需 要 在 这 n 个 位 置 中 插 入 数 , 而 插 入 的 数 为 [ 1 , m ] ( 大 家 应 该 能 看 出 来 ) 。 假如连续一段有n个0,所以需要在这n个位置中插入数,而插入的数为[1,m](大家应该能看出来)。 n0n[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]1a1mamiai
所 以 要 将 这 些 数 字 插 入 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+...+am0ain

等 价 于 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},每一种方案为一种序列。 nmai01n+mCn+m1m1

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+(ni+1)

对 于 一 段 连 续 为 0 的 区 间 , 就 不 需 要 考 虑 会 取 同 样 的 值 , 直 接 在 可 以 选 择 的 数 中 选 出 n 个 即 可 。 对于一段连续为0的区间,就不需要考虑会取同样的值,直接在可以选择的数中选出n个即可。 0n

假 设 可 以 取 的 值 有 m 个 , 那 么 方 案 数 为 C m n 假设可以取的值有m个,那么方案数为C_{m}^n mCmn

注 意 0 和 n 位 置 也 要 处 理 。 注意0和n位置也要处理。 0n

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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值