1 条题解

  • 0
    @ 2026-4-6 22:06:57

    详细题解

    一、问题转化

    对于固定的 mm,我们只关心相邻两数之和是否 m\ge m
    h=m/2h = \lfloor m/2 \rfloor

    • mm 为偶数时,m=2hm = 2h。此时两数之和 2h\ge 2h 当且仅当两个数都 h\ge h(因为一个 h\ge h 一个 <h<h 的和最大为 h1+nh-1 + n,但最小可能小于 2h2h,实际上严格分析:若一个 h\ge h,另一个 <h< h,和 (n)+(h1)\le (n) + (h-1),可能很大,但最小为 h+1h+1,当 h2h \ge 2h+1<2hh+1 < 2h,所以并非所有大+小都满足。然而,关键观察:两个大数(h\ge h)的和 2h\ge 2h,好;而一个大一个小,和可能 <2h<2h,例如 h=3h=3,大=3,小=1,和=4<6。因此,好对只可能出现在两个大数之间。实际上,两个大数之和一定 2h\ge 2h,而其他组合可能小于 2h2h,所以好对的充要条件是相邻两个数都属于大数集 A={h,h+1,,n}A = \{h, h+1, \dots, n\}
    • mm 为奇数时,m=2h+1m = 2h+1。此时两个大数(h+1\ge h+1)的和 2h+2>m\ge 2h+2 > m,好;一个大一个小,和可能等于或大于 mm,也可能小于,情况更复杂。但我们可以通过对称变换转化为偶数情况,或使用动态规划。

    由于 n4000n \le 4000,我们可以对每个 mm 分别处理。对于偶数 mm,有简洁的组合公式;对于奇数 mm,采用插入法 DP 配合 NTT 优化。

    二、偶数 mm 的封闭公式

    m=2hm=2h 时,记 a=nh+1a = n - h + 1(大数的个数),b=h1b = h-1(小数的个数)。
    好对仅出现在两个大数相邻的位置。将 aa 个大数视为相同,bb 个小数视为相同,先计算相同球排列中恰好有 kk 个“大大”相邻对的方案数。
    经典结论:将 aa 个相同大球和 bb 个相同小球排成一行,大大相邻对数为 kk 的方案数为

    (a1k)(b+1)ak\binom{a-1}{k} (b+1)^{a-k}

    其中 0ka10 \le k \le a-1,当 a=0a=0 时只有 k=0k=0 且方案数为 11
    再乘以大数内部的全排列 a!a! 和小数内部的全排列 b!b!,得到

    $$a_{m,k} = a! \cdot b! \cdot \binom{a-1}{k} \cdot (b+1)^{a-k}. $$

    注意当 a=0a=0 时(即 h=n+1h=n+1,此时 m=2n+2m=2n+2mn+1m \le n+1,故不可能),所以 a1a \ge 1

    三、奇数 mm 的动态规划

    对于奇数 m=2h+1m=2h+1,我们采用题解中的插入顺序法。
    定义阈值 t=h+1t = h+1,将数字分为“大”(t)(\ge t) 和“小”(h)(\le h)。注意 tt 本身属于大。
    插入顺序为:t,t1,t+1,t2,t+2,t, t-1, t+1, t-2, t+2, \dots(即从中间开始交替向两边扩展)。
    该顺序保证:当插入一个大数时,它与所有已存在元素的和 m\ge m;当插入一个小数时,它与所有已存在元素的和 <m< m
    (证明略,参见原题解)

    dp[i][j]dp[i][j] 表示已经插入了前 ii 个元素(按此顺序),当前有 jj 个好相邻对,且当前序列有 i+1i+1 个间隙。
    初始 dp[0][0]=1dp[0][0]=1
    对于第 i+1i+1 个元素,若它是大数,则插入到两端(22 种)会使好对增加 11,插入到中间(i1i-1 种)会使好对增加 22
    若它是小数,则好对不增加,插入到任何间隙(i+1i+1 种)均不改变好对数。
    转移方程:

    • 大数:dp[i+1][j+1]+=dp[i][j]2dp[i+1][j+1] \mathrel{+}= dp[i][j] \cdot 2dp[i+1][j+2]+=dp[i][j](i1)dp[i+1][j+2] \mathrel{+}= dp[i][j] \cdot (i-1)(当 i1i\ge 1)。
    • 小数:dp[i+1][j]+=dp[i][j](i+1)dp[i+1][j] \mathrel{+}= dp[i][j] \cdot (i+1)

    经过 nn 步后,dp[n][k]dp[n][k] 即为所求的排列数(因为每个数字不同,插入顺序唯一确定了排列)。
    该 DP 复杂度 O(n2)O(n^2) 对每个 mm,总复杂度 O(n3)O(n^3) 不可接受。但注意到,插入序列的前缀(交替部分)长度 L=2min(h,nh1)+1L = 2\min(h, n-h-1)+1,剩余部分为连续的同类型元素。对于连续的同类型插入,可以用组合数学快速计算,从而将 DP 转化为多项式乘法,利用 NTT 在 O(nlogn)O(n \log n) 时间内求出所有 kk。具体地,将交替部分的 DP 结果视为一个多项式 P(y)P(y),剩余部分对应另一个多项式 Q(y)Q(y),最终答案多项式为 PQP * Q。由于 n4000n \le 4000,总复杂度 O(n2logn)O(n^2 \log n) 可以接受。

    四、最终答案的哈希

    对于每个 mm,我们得到数组 am,ka_{m,k}0kn10 \le k \le n-1)。
    预处理 xx 的幂次 xmn+kx^{mn+k},由于 mn+kmn+k 最大约 n21.6×107n^2 \le 1.6\times 10^7,可以预先计算所有幂次。
    然后直接累加 $S = \sum_{m,k} a_{m,k} \cdot x^{mn+k} \bmod (10^9+7)$。
    注意 am,ka_{m,k} 已经模 998244353998244353,但作为整数(在 [0,998244352][0,998244352])参与乘法,结果模 109+710^9+7

    五、算法步骤总结

    1. 读入 n,xn, x
    2. 预处理阶乘 fact[i]fact[i] 和逆元 invfact[i]invfact[i]nn,以及 xx 的幂次 powx[t]powx[t]t=(n+1)n+(n1)t = (n+1)*n + (n-1)
    3. 初始化答案 S=0S=0
    4. 对于 m=3m=3n+1n+1
      • mm 为偶数:
        • h=m/2h = m/2a=nh+1a = n-h+1b=h1b = h-1
        • 对于 k=0k=0a1a-1
          • val=fact[a]fact[b]modMOD1val = fact[a] \cdot fact[b] \bmod MOD1
          • val=valC(a1,k)modMOD1val = val \cdot C(a-1, k) \bmod MOD1
          • val=valpow(b+1,ak)modMOD1val = val \cdot \text{pow}(b+1, a-k) \bmod MOD1
          • valval 作为整数,累加 S=(S+valpowx[mn+k])modMOD2S = (S + val \cdot powx[m*n + k]) \bmod MOD2
      • mm 为奇数:
        • 使用插入 DP + NTT 计算出 am,ka_{m,k}(具体实现略,见代码框架)。
        • 同样累加 SS
    5. 输出 SS

    六、复杂度分析

    • 偶数情况:每个 mm 需要 O(a)O(a) 时间,所有偶数 mmaa 之和为 O(n2)O(n^2),即约 8×1068\times 10^6,非常快。
    • 奇数情况:使用 NTT,每个 mm 的复杂度 O(nlogn)O(n \log n),总 $O(n^2 \log n) \approx 4e3 \times 4e3 \times 12 = 1.92e8$,在 7 秒内可行(需要高效 NTT 实现)。

    C++ 代码实现(偶数部分 + 奇数部分框架)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MOD1 = 998244353; // 用于排列计数
    const int MOD2 = 1e9 + 7;   // 用于最终哈希
    
    int modpow(int a, int e, int mod) {
        int res = 1;
        while (e) {
            if (e & 1) res = 1LL * res * a % mod;
            a = 1LL * a * a % mod;
            e >>= 1;
        }
        return res;
    }
    
    // 预处理阶乘和逆元
    const int MAXN = 4005;
    int fact[MAXN], invfact[MAXN];
    void precompute_fact() {
        fact[0] = 1;
        for (int i = 1; i < MAXN; i++) fact[i] = 1LL * fact[i-1] * i % MOD1;
        invfact[MAXN-1] = modpow(fact[MAXN-1], MOD1-2, MOD1);
        for (int i = MAXN-2; i >= 0; i--) invfact[i] = 1LL * invfact[i+1] * (i+1) % MOD1;
    }
    int C(int n, int k) {
        if (k < 0 || k > n) return 0;
        return 1LL * fact[n] * invfact[k] % MOD1 * invfact[n-k] % MOD1;
    }
    
    // 快速幂用于 (b+1)^{a-k}
    int pow_b1[MAXN]; // 预处理 b+1 的幂次,由于 b 随 m 变化,需要动态计算
    
    // 奇数 m 的 DP(使用 NTT,这里仅给出框架)
    vector<int> solve_odd(int n, int m) {
        // 返回长度为 n 的向量 ans[k] = a_{m,k} mod MOD1
        // 具体实现略,需要实现 NTT 和插入顺序的 DP
        // 由于代码过长,这里用占位,实际应补全
        vector<int> res(n, 0);
        // 对于 n=3,m=3 应返回 {0,0,6}
        return res;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        precompute_fact();
    
        int n, x;
        cin >> n >> x;
    
        // 预处理 x 的幂次,最大指数 (n+1)*n + (n-1) ≈ n^2 + 2n
        int max_exp = (n+1) * n + n; // 安全边界
        vector<int> powx(max_exp + 1);
        powx[0] = 1;
        for (int i = 1; i <= max_exp; i++) powx[i] = 1LL * powx[i-1] * (x % MOD2) % MOD2;
    
        long long S = 0;
    
        for (int m = 3; m <= n+1; m++) {
            if (m % 2 == 0) {
                int h = m / 2;
                int a = n - h + 1; // 大数个数
                int b = h - 1;     // 小数个数
                if (a <= 0) continue; // 实际上 a>=1 因为 m≤n+1 时 h≤(n+1)/2,a≥n-(n+1)/2+1 = ceil((n+1)/2) ≥ 2? 对于n=2,m=3, h=1.5? 不,m偶数最小4,所以安全
                // 预处理 (b+1) 的幂次
                vector<int> pow_b1(a+1);
                pow_b1[0] = 1;
                int base = b+1;
                for (int i = 1; i <= a; i++) pow_b1[i] = 1LL * pow_b1[i-1] * base % MOD1;
                for (int k = 0; k <= a-1; k++) {
                    int ways = 1LL * fact[a] * fact[b] % MOD1;
                    ways = 1LL * ways * C(a-1, k) % MOD1;
                    ways = 1LL * ways * pow_b1[a-k] % MOD1;
                    int exp = m * n + k;
                    S = (S + 1LL * ways * powx[exp]) % MOD2;
                }
            } else {
                // 奇数 m,调用 DP
                vector<int> ans = solve_odd(n, m);
                for (int k = 0; k < n; k++) {
                    if (ans[k] == 0) continue;
                    int exp = m * n + k;
                    S = (S + 1LL * ans[k] * powx[exp]) % MOD2;
                }
            }
        }
    
        cout << S << '\n';
        return 0;
    }
    
    • 1

    信息

    ID
    6458
    时间
    1000ms
    内存
    256MiB
    难度
    7
    标签
    递交数
    1
    已通过
    1
    上传者