1 条题解

  • 0
    @ 2025-11-6 11:42:48

    划分 题解

    题目分析

    这道题要求将数组划分为若干段,使得每段的和递增,并且最小化各段和的平方和。

    关键约束

    • 段内元素连续
    • 段和必须单调非递减
    • 目标是最小化各段和的平方和

    解题思路

    核心观察

    1. 贪心性质:为了让平方和最小,应该让段尽可能均匀
    2. 单调队列:使用单调队列优化动态规划
    3. 决策单调性:最优决策点具有单调性

    算法设计

    代码采用了单调队列优化DP的方法:

    主要步骤:

    1. 前缀和预处理:计算前缀和数组
    2. 单调队列维护:维护可能的最优决策点
    3. 二分查找:快速找到最优决策点
    4. 高精度计算:使用__int128存储结果

    代码详解

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    
    const int N = 40000005;
    int n, t, tl, tr;
    int a[N];          // 前缀和数组
    signed la[N], stk[N];  // la: 决策点,stk: 单调队列
    
    namespace IN {
        int x, y, z, b1, b2, id, p, l, r;
        
        // 初始化生成器参数
        inline void pre() {
            cin >> x >> y >> z >> b1 >> b2 >> p >> p >> l >> r;
        }
        
        // 生成下一个a_i
        inline int gt() {
            if (!t) {  // type=0,直接输入
                cin >> x;
                return x;
            }
            
            // type=1,使用生成器
            if (++id > p) 
                cin >> p >> l >> r;
                
            int c = b1 % (r - l + 1) + l;
            int b3 = (b1 * y + b2 * x + z) & ((1 << 30) - 1);
            b1 = b2;
            b2 = b3;
            return c;
        }
    }
    
    // 输出__int128
    void pr(__int128 x) {
        if (x >= 10) pr(x / 10);
        cout << (char)(x % 10 + '0');
    }
    
    // 计算斜率:2*a[x] - a[la[x]]
    inline int gt(int x) {
        return a[x] * 2 - a[la[x]];
    }
    
    // 二分查找最优决策点
    inline int gtl(int x) {
        int l = tl, r = tr - 1;
        while (l < r) {
            int m = (l + r + 1) >> 1;
            if (gt(stk[m]) <= x) l = m;
            else r = m - 1;
        }
        return stk[l];
    }
    
    void run() {
        cin >> n >> t;
        
        if (t) IN::pre();  // 初始化生成器
        
        tr = 1;  // 队列尾指针
        int x = 0;  // 当前前缀和
        
        // 动态规划过程
        for (int i = 1; i <= n; i++) {
            a[i] = (x += IN::gt());  // 计算前缀和
            
            // 维护队列头部:删除不满足条件的决策点
            while (tl + 1 < tr && gt(stk[tl + 1]) <= x)
                tl++;
                
            // 找到当前最优决策点
            int c = la[i] = gtl(x);
            int y = x * 2 - a[c];  // 计算斜率
            
            // 维护队列尾部:保持单调性
            while (tr > tl && gt(stk[tr - 1]) >= y)
                tr--;
                
            stk[tr++] = i;  // 加入新决策点
        }
        
        // 计算最终答案
        __int128 ans = 0;
        x = n;
        while (x) {
            int y = la[x];
            ans += ((__int128)a[x] - a[y]) * (a[x] - a[y]);
            x = y;
        }
        
        pr(ans);
    }
    
    signed main() {
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        int T = 1;
        while (T--) run();
        return 0;
    }
    

    算法原理详解

    1. 问题建模

    a[i] 为前缀和,f[i] 为前i个元素的最小平方和,la[i] 记录最优决策点。

    状态转移:

    f[i] = min_{j} { f[j] + (a[i]-a[j])² }
    约束条件:a[i]-a[j] ≥ a[j]-a[la[j]]
    

    2. 斜率优化

    将约束条件改写为:

    a[i] ≥ 2a[j] - a[la[j]]
    

    定义斜率 g(j) = 2a[j] - a[la[j]],则决策点j对i有效当且仅当 g(j) ≤ a[i]

    3. 单调队列维护

    • 队列头部:删除 g(stk[tl+1]) ≤ a[i] 的点,因为它们对后续决策不再重要
    • 队列尾部:维护 g(stk) 的单调递增性,保证决策最优性

    4. 二分查找

    使用二分在单调队列中找到满足 g(j) ≤ a[i] 的最大j,即为当前最优决策点。

    复杂度分析

    • 时间复杂度:O(n),每个元素入队出队一次
    • 空间复杂度:O(n),存储前缀和和队列

    样例解析

    样例1:

    5 0
    5 1 7 9 9
    

    前缀和:5, 6, 13, 22, 31

    最优划分:

    • 段1: 5+1=6
    • 段2: 7
    • 段3: 9
    • 段4: 9

    平方和:6² + 7² + 9² + 9² = 36 + 49 + 81 + 81 = 247

    关键技巧

    1. 斜率优化:将约束条件转化为斜率比较
    2. 单调队列:高效维护可能的最优决策点
    3. 二分查找:快速定位最优决策
    4. 高精度处理:使用__int128避免溢出
    5. 数据生成优化:支持大规模数据输入

    总结

    这道题的解题亮点:

    1. 问题转化:将划分问题转化为斜率优化DP
    2. 高效算法:O(n)时间解决大规模问题
    3. 单调性利用:充分利用决策单调性优化
    4. 工程实现:处理了大数据生成和高精度计算

    算法通过深入的数学分析和巧妙的数据结构设计,在O(n)时间内解决了这个复杂的优化问题,展示了处理大规模动态规划问题的高级技巧。

    • 1

    信息

    ID
    5039
    时间
    2000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    5
    已通过
    1
    上传者