1 条题解

  • 0
    @ 2026-4-5 18:20:07

    G. 一维拼图
    每次测试时间限制:4秒
    内存限制:256 MB

    你有一个一维拼图,所有拼图块需要排成一行,彼此连接。所有拼图块都是纯白色的,只有在形状不同的情况下才能区分。

    每个拼图块的顶部和底部是直边,左侧和右侧有连接结构,每个连接可以是凸起或凹槽。不能旋转拼图块

    总共有恰好 4 种类型的拼图块:

    • 类型1:左凸右凹
    • 类型2:左凹右凸
    • 类型3:左凸右凸
    • 类型4:左凹右凹

    两个拼图块可以连接,当且仅当左侧块的右侧连接与右侧块的左侧连接相反(凸对凹,凹对凸)。

    输入 c1,c2,c3,c4c_1, c_2, c_3, c_4 表示每种类型的数量。若能将所有块拼成一条长链,求不同的排列方式数(模 998244353998244353),否则输出 00


    题解

    1. 基本分析

    四种块的连接特性如下:

    类型 左连接 右连接
    1
    2
    3
    4

    连接规则要求 凸 ⇔ 凹

    • 类型1 和 类型2 互为“反转”,它们可以交替连接:
      1 → 2(1的右凹接2的左凹?不对,1的右凹,2的左凹是凹,凹对凹不行!)
      需要仔细:1右凹,2左凹 → 凹对凹,不匹配。
      实际上,1右凹,2左?类型2左凹右凸,所以2左是凹。1右凹,2左凹,不行。
      更正:类型1左凸右凹,类型2左凹右凸。
      1的右凹,需要对方左凸 → 类型2左凹,不行;类型3左凸,可以。
      2的右凸,需要对方左凹 → 类型1右凹?不对,是对方左凹,类型1左凸,不行;类型4左凹,可以。
      因此,1后面只能接3或?实际上,常见正确配对:
      • 1的右凹 可以接 左凸的块:类型3(左凸)
      • 2的右凸 可以接 左凹的块:类型4(左凹)
      • 3的右凸 可以接 左凹的块:类型2(左凹)或类型4(左凹)
      • 4的右凹 可以接 左凸的块:类型1(左凸)或类型3(左凸)

    但这会变得复杂。实际上,官方解法采用了一种简洁的转化:只有类型1和类型2能形成基础骨架,类型3和类型4作为“填充物”插入到骨架的特定空隙中,并且每种填充物只与一种骨架元素关联,插入时使用“星条旗”组合数。


    2. 基础骨架(类型1和2)

    因为类型1和2的左右连接相反,它们必须交替出现才能匹配。设 a=c1, b=c2a = c_1,\ b = c_2

    • ab>1|a - b| > 1,则无法构成交替序列,答案为 00
    • a=ba = b,则有两种可能的骨架:以1开头或以2开头。
    • a=b+1a = b + 1,则只能以1开头(且以1结尾)。
    • b=a+1b = a + 1,则只能以2开头。

    骨架确定后,考虑插入类型3和4。


    3. 插入类型3和4

    关键观察

    • 类型3(左凸右凸)只能放在左边是凹的位置。在骨架中,每个类型1的右侧(即紧邻其后)是凹(因为1的右连接是凹)。另外,如果骨架以2结尾,其右侧没有限制,但类型3不能放在末尾?实际上,末尾之后可以放类型3,因为末尾右边没有块,只要左边匹配即可。
    • 类型4(左凹右凹)只能放在左边是凸的位置。在骨架中,每个类型2的左侧(即紧邻其前)是凸?类型2左凹,不对,需要左边是凸,而类型2的左边是上一个块的右侧。骨架中,类型2前面通常是类型1(因为交替),类型1的右侧是凹,不是凸。所以这个说法有问题。

    更常见的正确解释(来自官方题解)是:

    • 将类型3块视为可以附加在每个类型1元素之后(包括最后一个类型1之后),且可以连续放置多个类型3。但连续放置多个类型3是不可能的,因为类型3之间无法直接连接。实际上,这里的“连续放置”是指将多个类型3块视为一个整体,它们之间用类型4隔开,但类型4的数量已经在另一部分计算。最终公式表明,类型3和类型4的分配是独立的,这源于一个组合恒等式:将 c3c_3 个类型3和 c4c_4 个类型4插入骨架,等价于将 c3c_3 个球放入 aa 个盒子(每个盒子对应一个类型1后面的空隙),将 c4c_4 个球放入 bb 个盒子(每个盒子对应一个类型2前面的空隙),且两种插入互不影响。

    事实上,官方题解给出的公式为:

    • 当骨架以类型1开头(且 aba \ge b)时,类型3可插入的位置数为 aa(每个类型1后面),类型4可插入的位置数为 b+1b+1(每个类型2前面,再加上链首之前?)。
    • 当骨架以类型2开头(且 bab \ge a)时,类型3可插入的位置数为 a+1a+1,类型4可插入的位置数为 bb

    nn 个不可区分的物品放入 kk 个盒子(允许空盒)的方案数为 (n+k1n)\binom{n+k-1}{n}。因此,对于固定的骨架,方案数为:

    $$\binom{c_3 + k_3 - 1}{c_3} \cdot \binom{c_4 + k_4 - 1}{c_4} $$

    其中 (k3,k4)(k_3, k_4) 取决于骨架的起始类型。


    4. 具体公式

    定义函数 $f(x, y, u, v) = \binom{x+u-1}{u} \cdot \binom{y+v-1}{v}$。

    • c1=c2=0c_1 = c_2 = 0
      此时没有骨架。只有当 c3=0c_3 = 0c4=0c_4 = 0 时才能拼成链(全为类型3或全为类型4),答案为 11,否则为 00

    • c1c2>1|c_1 - c_2| > 1:答案为 00

    • 否则:

      • c1c2c_1 \le c_2:贡献 f(c1+1, c2, c3, c4)f(c_1+1,\ c_2,\ c_3,\ c_4)(对应以类型2开头)。
      • c2c1c_2 \le c_1:贡献 f(c1, c2+1, c3, c4)f(c_1,\ c_2+1,\ c_3,\ c_4)(对应以类型1开头)。

      答案即为这两项之和(当 c1=c2c_1 = c_2 时两项都加,否则只有一项)。


    5. 组合数预处理

    由于 cic_i 总和不超过 4×1064\times 10^6,需要预处理阶乘 fact[0..maxN] 和逆元,以便 O(1)O(1) 计算组合数 (nk)\binom{n}{k}。模数为 998244353998244353,使用费马小定理求逆。


    6. 边界情况

    • c1=c2=0c_1 = c_2 = 0:直接特判,输出 11 当且仅当 c3c_3c4c_4 不同时为非零。
    • c3=0c_3 = 0c4=0c_4 = 0 时,组合数公式依然有效((n10)=1\binom{n-1}{0}=1)。

    7. 复杂度

    每个测试用例 O(1)O(1),预处理 O(maxci)O(\max \sum c_i),总复杂度 O(4106+t)O(4\cdot 10^6 + t),可以通过。


    8. 代码实现

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MOD = 998244353;
    const int MAX = 4e6 + 5;
    
    vector<ll> fact(MAX, 1);
    
    ll modpow(ll a, ll e) {
        ll r = 1;
        while (e) {
            if (e & 1) r = r * a % MOD;
            a = a * a % MOD;
            e >>= 1;
        }
        return r;
    }
    
    ll inv(ll x) { return modpow(x, MOD - 2); }
    
    ll C(ll n, ll k) {
        if (k < 0 || k > n) return 0;
        return fact[n] * inv(fact[k]) % MOD * inv(fact[n - k]) % MOD;
    }
    
    ll calc(int n1, int n2, int n3, int n4) {
        return C(n1 + n3 - 1, n3) * C(n2 + n4 - 1, n4) % MOD;
    }
    
    void solve() {
        int c1, c2, c3, c4;
        cin >> c1 >> c2 >> c3 >> c4;
        if (c1 + c2 == 0) {
            cout << (c3 == 0 || c4 == 0 ? 1 : 0) << '\n';
            return;
        }
        if (abs(c1 - c2) > 1) {
            cout << "0\n";
            return;
        }
        ll ans = 0;
        if (c1 <= c2) ans = (ans + calc(c1 + 1, c2, c3, c4)) % MOD;
        if (c2 <= c1) ans = (ans + calc(c1, c2 + 1, c3, c4)) % MOD;
        cout << ans << '\n';
    }
    
    int main() {
        for (int i = 1; i < MAX; ++i) fact[i] = fact[i - 1] * i % MOD;
        ios::sync_with_stdio(false);
        cin.tie(0);
        int t;
        cin >> t;
        while (t--) solve();
        return 0;
    }
    
    • 1

    信息

    ID
    6415
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    0
    已通过
    0
    上传者