• 数数
  • 数数题解 · muse 机器人自动生成

  • @ 2026-10-26 14:47:14

我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目简述

有四种物品,数量分别是 n1n_1、n2n_2、n3n_3、n4n_4,同一种物品不区分先后。把所有物品排成一排,要求任意两个相邻物品的种类都不同,求方案数,对 109+710^9 + 7 取模。

数据范围是 0≤n1,n2≤2000 \le n_1, n_2 \le 200,0≤n3,n4≤5×1040 \le n_3, n_4 \le 5 \times 10^4。

解题思路

关键一步是:先排数量大的 33 号和 44 号,把它们排成的串叫“骨架”,再把数量少的 11 号和 22 号塞进骨架的空位里。

骨架里相邻两个位置之间、以及两端,都是一个“空位”。骨架中相邻相同的那一对(比如两个 33 挨着),它中间的空位叫“坏空位”,必须至少塞一个小物品进去把它隔开;其余空位叫“好空位”,可以空着也可以塞。

一个空位里塞的小物品只能是 11 号和 22 号,而且在同一个空位里它们必须交替出现(不然空位内部就相邻相同了)。这里用一个叫“生成函数”的工具:它就是把“放 aa 个 11 号、bb 个 22 号有多少种”记成 xaybx^a y^b 的系数,方便统一算。一个空位里放 aa 个 11 号、bb 个 22 号:若 a=ba = b 有两种开头(11 开头或 22 开头),若 aa 和 bb 相差 11 只有一种,否则 00 种。把这些情况加起来,一个好空位对应的式子正好化简成:

(1+x)(1+y)1−xy\frac{(1+x)(1+y)}{1-xy}

一个坏空位就是好空位去掉“空着”这一种情况。

小物品总数最多只有 400400 个,所以坏空位数 BB 最多也是 400400,骨架离“完全交替”不会太远。把 33 号分成 r3r_3 段、44 号分成 r4r_4 段,那么 B=(n3−r3)+(n4−r4)B = (n_3 - r_3) + (n_4 - r_4)。对每个 BB,骨架的种数只和 BB 以及 n3−n4n_3 - n_4 有关:按两段数之差分三种情况,系数是 22 或 11(段数相等时骨架可以 33 开头也可以 44 开头,系数是 22;段数相差 11 时开头被定死,系数是 11),再乘上组合数。

对每个 BB,把小物品塞进空位的方案数用递推算:记 HBH_B 为当前 BB 对应的系数表,从 BB 到 B+1B+1 相当于把一个好空位换成坏空位,也就是乘上 (x+y+2xy)(x + y + 2xy) 再除以 (1+x)(1+y)(1+x)(1+y);除法按行列递推做,比如除以 (1+x)(1+x) 时用 U[a][b]=T[a][b]−U[a−1][b]U[a][b] = T[a][b] - U[a-1][b] 这样的递推。最后再乘上 (1−xy)(1-xy) 的展开,把所有 BB 的贡献加起来。

两种特殊情况单独处理:没有大物品时只能 11、22 交替排;只有一种大物品时骨架只有一段,坏空位数就是该物品数减 11。

算法流程

  1. 预处理阶乘和逆元,方便 O(1)O(1) 算组合数。
  2. 没有大物品时按交替规则直接回答;只有一种大物品时按固定坏空位数算插入方案。
  3. 两种大物品都有时,枚举坏空位数 BB(不超过 400400),分别算骨架种数和插入方案数,相乘后累加。
  4. 插入方案数的系数表按 BB 递推更新,不用每次重算。

小例子 walkthrough

样例是 0 1 2 30\ 1\ 2\ 3,即 11 个 22 号、22 个 33 号、33 个 44 号。

B=0B = 0 时骨架必须完全交替,只能是 4343443434,11 个 22 号可以塞进 66 个空位的任意一个,贡献 66 种。

B=1B = 1 时骨架恰有一处相邻相同,共有 44 条:3434434344、3443434434、4344343443、4434344343,每条的坏空位只有一个,11 个 22 号只能塞进那里,贡献 44 种。

6+4=106 + 4 = 10,正好是样例输出。

样例

样例输入

0 1 2 3

样例输出

10

AC 代码 (C++)

#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1000000007LL;
long long modpow(long long a, long long e) {
    long long r = 1;
    while (e) {
        if (e & 1) r = r * a % MOD;
        a = a * a % MOD;
        e >>= 1;
    }
    return r;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    long long n1, n2, n3, n4;
    if (!(cin >> n1 >> n2 >> n3 >> n4)) return 0;
    const int MAXF = 300000;
    vector<long long> fac(MAXF + 1), ifac(MAXF + 1);
    fac[0] = 1;
    for (int i = 1; i <= MAXF; ++i) fac[i] = fac[i - 1] * i % MOD;
    ifac[MAXF] = modpow(fac[MAXF], MOD - 2);
    for (int i = MAXF; i > 0; --i) ifac[i - 1] = ifac[i] * i % MOD;
    auto C = [&](long long n, long long k) -> long long {
        if (k < 0 || k > n || n < 0) return 0;
        return fac[n] * ifac[k] % MOD * ifac[n - k] % MOD;
    };
    long long ans = 0;
    if (n3 == 0 && n4 == 0) {
        // 只有 1、2 两种,只能交替排
        if (n1 == 0 && n2 == 0) ans = 1;
        else if (n1 == n2) ans = 2;
        else if (llabs(n1 - n2) == 1) ans = 1;
        else ans = 0;
        cout << ans << '\n';
        return 0;
    }
    if (n3 > 0 && n4 > 0) {
        long long N34 = n3 + n4;
        long long S = N34 + 1; // 坏空位数 + 好空位数
        long long Bmax = min(n1 + n2, n3 + n4 - 2);
        // H[a][b] 是 (x+y+2xy)^B (1+x)^(S-B) (1+y)^(S-B) 中 x^a y^b 的系数
        vector<vector<long long>> H(n1 + 1, vector<long long>(n2 + 1, 0));
        for (long long a = 0; a <= n1; ++a)
            for (long long b = 0; b <= n2; ++b)
                H[a][b] = C(S, a) * C(S, b) % MOD;
        long long D = n3 - n4;
        for (long long B = 0; B <= Bmax; ++B) {
            // 插入小物品:乘上 (1-xy)^(-S) 的展开
            long long ins = 0;
            for (long long t = 0; t <= min(n1, n2); ++t)
                ins = (ins + C(S + t - 1, t) * H[n1 - t][n2 - t]) % MOD;
            // 大物品骨架:按段数差加权求和
            long long sb = 0;
            auto addTerm = [&](long long diff, long long w) {
                if ((B + diff) % 2 != 0) return;
                long long d3 = (B + diff) / 2;
                long long d4 = B - d3;
                if (d3 < 0 || d4 < 0 || d3 > n3 - 1 || d4 > n4 - 1) return;
                sb = (sb + w * C(n3 - 1, d3) % MOD * C(n4 - 1, d4)) % MOD;
            };
            addTerm(D, 2);
            addTerm(D - 1, 1);
            addTerm(D + 1, 1);
            ans = (ans + sb * ins) % MOD;
            if (B == Bmax) break;
            // H 乘 (x+y+2xy) 再除以 (1+x)(1+y),得到 B+1 的情形
            vector<vector<long long>> T(n1 + 1, vector<long long>(n2 + 1, 0));
            for (long long a = 0; a <= n1; ++a)
                for (long long b = 0; b <= n2; ++b) {
                    long long v = 0;
                    if (a > 0) v += H[a - 1][b];
                    if (b > 0) v += H[a][b - 1];
                    if (a > 0 && b > 0) v += 2 * H[a - 1][b - 1];
                    T[a][b] = v % MOD;
                }
            vector<vector<long long>> U(n1 + 1, vector<long long>(n2 + 1, 0));
            for (long long a = 0; a <= n1; ++a)
                for (long long b = 0; b <= n2; ++b) {
                    long long v = T[a][b];
                    if (a > 0) v -= U[a - 1][b];
                    U[a][b] = (v % MOD + MOD) % MOD;
                }
            vector<vector<long long>> Hn(n1 + 1, vector<long long>(n2 + 1, 0));
            for (long long a = 0; a <= n1; ++a)
                for (long long b = 0; b <= n2; ++b) {
                    long long v = U[a][b];
                    if (b > 0) v -= Hn[a][b - 1];
                    Hn[a][b] = (v % MOD + MOD) % MOD;
                }
            H.swap(Hn);
        }
        cout << ans % MOD << '\n';
        return 0;
    }
    // 只有一种大物品:骨架只有一段,坏空位数 = 该物品数 - 1
    long long L = max(n3, n4);
    long long B0 = L - 1;
    if (B0 > n1 + n2) { cout << 0 << '\n'; return 0; }
    vector<vector<long long>> cur(n1 + 1, vector<long long>(n2 + 1, 0));
    for (long long a = 0; a <= n1; ++a)
        for (long long b = 0; b <= n2; ++b)
            cur[a][b] = C(2, a) * C(2, b) % MOD;
    for (long long rep = 0; rep < B0; ++rep) {
        vector<vector<long long>> nxt(n1 + 1, vector<long long>(n2 + 1, 0));
        for (long long a = 0; a <= n1; ++a)
            for (long long b = 0; b <= n2; ++b) {
                long long v = 0;
                if (a > 0) v += cur[a - 1][b];
                if (b > 0) v += cur[a][b - 1];
                if (a > 0 && b > 0) v += 2 * cur[a - 1][b - 1];
                nxt[a][b] = v % MOD;
            }
        cur.swap(nxt);
    }
    for (long long t = 0; t <= min(n1, n2); ++t)
        ans = (ans + C(B0 + t + 1, t) * cur[n1 - t][n2 - t]) % MOD;
    cout << ans % MOD << '\n';
    return 0;
}

0 comments

No comments so far...

Information

ID
207
Time
ms
Memory
MiB
Difficulty
8
Tags
(None)
# Submissions
25
Accepted
5
Uploaded By