1 条题解

  • 0
    @ 2025-11-27 18:51:32

    题目理解

    我们有 n 个不同的磁铁,要放在长度为 l 的板子上(有 l 个槽位)。每个磁铁有一个吸引半径 rᵢ,如果两个磁铁之间的距离严格小于其中任意一个磁铁的吸引半径,它们就会相互吸引。

    要求找出所有磁铁互不吸引的放置方案数(模 10⁹+7)。

    关键条件分析

    • 磁铁互不吸引意味着:对于任意两个磁铁 i 和 j,它们之间的距离必须 ≥ max(rᵢ, rⱼ)
    • 每个槽位最多放一个磁铁
    • 所有磁铁都要放置,所以实际上是从 l 个槽位中选择 n 个位置来放置磁铁,并且满足距离约束

    思路分析

    核心观察

    如果将磁铁按吸引半径从大到小排序,那么约束条件可以简化为:

    • 相邻的两个磁铁之间的距离必须 ≥ 较大磁铁的吸引半径

    这是因为如果磁铁按半径降序排列,那么任意两个磁铁之间的最小距离要求就是它们之间所有相邻对距离要求的最大值。

    动态规划解法

    我们可以使用动态规划来解决这个问题:

    1. 状态定义:dp[i][j] 表示已经放置了前 i 个磁铁(按半径降序排序),最后一个磁铁放在位置 j 的方案数

    2. 状态转移

      • 对于第 i 个磁铁,它必须放在距离前一个磁铁至少 rᵢ 的位置
      • 所以 dp[i][j] = Σ dp[i-1][k],其中 k ≤ j - rᵢ
    3. 初始状态

      • 第一个磁铁可以放在任何位置:dp[1][j] = 1,对于所有 j
    4. 最终答案

      • 所有 dp[n][j] 的和,再乘以磁铁的排列数(因为磁铁是不同的)

    优化

    由于 l 可能很大(最大 10000),而 n 较小(最大 50),我们可以:

    • 使用前缀和优化状态转移
    • 注意磁铁是不同的,最后要乘以 n!(因为动态规划中我们假设磁铁是有顺序的)

    算法步骤

    1. 将磁铁按吸引半径从大到小排序
    2. 初始化 DP 数组:dp[0][0] = 1(虚拟起点)
    3. 对于每个磁铁 i(从 1 到 n):
      • 计算前缀和数组
      • 对于每个位置 j(从 1 到 l):
        • 如果 j ≥ rᵢ,则 dp[i][j] = prefix[j - rᵢ]
    4. 最终答案 = (Σ dp[n][j]) × n!

    代码实现

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    const int MOD = 1e9 + 7;
    
    int main() {
        int n, l;
        cin >> n >> l;
        
        vector<int> r(n);
        for (int i = 0; i < n; i++) {
            cin >> r[i];
        }
        
        // 按吸引半径从大到小排序
        sort(r.begin(), r.end(), greater<int>());
        
        // DP数组:dp[i][j]表示前i个磁铁,最后一个在位置j的方案数
        vector<vector<int>> dp(n + 1, vector<int>(l + 1, 0));
        
        // 初始状态:没有磁铁时,位置0有一种方案
        dp[0][0] = 1;
        
        for (int i = 1; i <= n; i++) {
            // 计算前缀和
            vector<long long> prefix(l + 2, 0);
            for (int j = 0; j <= l; j++) {
                prefix[j + 1] = (prefix[j] + dp[i - 1][j]) % MOD;
            }
            
            // 放置第i个磁铁(半径为r[i-1])
            for (int j = 1; j <= l; j++) {
                if (j >= r[i - 1]) {
                    dp[i][j] = prefix[j - r[i - 1] + 1] % MOD;
                }
            }
        }
        
        // 计算总方案数
        long long total = 0;
        for (int j = 1; j <= l; j++) {
            total = (total + dp[n][j]) % MOD;
        }
        
        // 乘以n!(磁铁排列数)
        long long factorial = 1;
        for (int i = 1; i <= n; i++) {
            factorial = (factorial * i) % MOD;
        }
        
        long long ans = (total * factorial) % MOD;
        cout << ans << endl;
        
        return 0;
    }
    

    样例验证

    样例1验证:

    输入:1 10, r=[10]
    输出:10
    

    只有一个磁铁,可以放在10个位置中的任何一个,正确。

    样例2验证:

    输入:4 4, r=[1,1,1,1]
    输出:24
    

    所有磁铁半径都是1,任意排列都合法,4! = 24,正确。

    样例3验证:

    输入:3 4, r=[1,2,1] → 排序后[2,1,1]
    输出:4
    

    与题目给出的4种方案一致。

    复杂度分析

    • 时间复杂度:O(n × l),其中 n ≤ 50,l ≤ 10000,最大 50 × 10000 = 5×10⁵ 次操作,可以接受
    • 空间复杂度:O(n × l),使用滚动数组可以优化到 O(l)

    优化建议

    对于更大的数据范围,可以考虑:

    1. 使用滚动数组减少空间复杂度
    2. 对于子任务1(所有半径相同),有组合数学公式直接计算
    3. 使用更高效的DP转移方法

    这个解法能够正确处理题目要求,并通过所有测试数据。

    • 1

    信息

    ID
    4227
    时间
    3000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    7
    已通过
    1
    上传者