zoukankan      html  css  js  c++  java
  • CSP2019 Day2T2 划分

    很显然有一个暴力 (dp),令 (dp_{i, j}) 表示最后一次划分在 (i) 上次划分在 (j) 的最小花费,令 (S_i = sumlimits_{j = 1} ^ i a_j)。那么有转移:

    [dp_{i, j} = min{dp_{j, k} + (S_i - S_j) ^ 2}(S_i - S_j ge S_j - S_k) ]

    可以发现 (dp_{j, k}) 的值是和 (i) 无关的,只是 (k) 的取值范围和 (i) 有关,那么我们只需要知道 (k) 的取值范围再取这个范围内的最小值即可。将后面那个条件移项可得 (S_k ge 2 imes S_j - S_i),因此 (k) 的取值范围应该在 (2 imes S_j - S_i sim j - 1),而当 (j) 固定时,因为 (S_i) 随着 (i) 的增大单调递增,所以 (k) 能取到的下界不断减小,于是我们可以考虑在 (i) 的同时维护每个 (j) 的下界以及当前能取到的最小 (dp_{j, k}) 这样均摊复杂度就是 (O(n ^ 2)) 的了。

    rep(i, 1, n) dp[i][0] = sum[i] * sum[i], p[i] = i - 1, mx[i] = inf;
    rep(i, 2, n){
        rep(j, 1, i - 1){
            while(p[j] >= 0 && sum[p[j]] >= 2 * sum[j] - sum[i]) mx[j] = min(mx[j], dp[j][p[j]]), --p[j];
            if(p[j] < j - 1) ++p[j]; 
            if(mx[j] != inf) dp[i][j] = mx[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]);
        }
    }
    rep(i, 0, n - 1) ans = min(ans, dp[n][i]);
    printf("%lld", ans);
    return 0;
    

    如果这个 (dp) 不能压缩状态是显然无法优化的,但为了保证题目中的条件,我们根本无法压缩状态,下面一个常见的手段就是可以打个决策转移的表。可以发现我们每次转移 (j ightarrow i) 都是选择了一个最大的能满足条件的 (k)(dp_{j, k} ightarrow dp_{i, j}),并且可以发现当 (j) 越大时 (dp_{i, j}) 越大。这样换句话来说就是每次我们让最后一段尽可能选择小的是更优的,实际上因为我们有 ((a + b) ^ 2 > a ^ 2 + b ^ 2) 因此我们要尽可能多分段,如果我们每次选择最小的一段,不仅能够分更多的段,并且还能让后面尽可能分的段更多,并且能让当前的贡献更小,所以这样做一定是更优的。针对这个贪心,我们可以令 (dp_i) 表示当前划分到以 (i) 结尾的段的最小花费,令 (p_i) 表示 (i) 是由哪里转移来的,那么每次转移就要找到一个最大 (j) 满足 (S_i - S_j ge S_j - S_{p_j}) 移项可得 (S_i ge 2 imes S_j - S_{p_j}),我们大可以使用线段树二分出左边第一个比某个数大的位置,但实际上这里还有个 (O(n)) 做法。可以发现因为 (S_i) 是单调递增的,因此我们之前能够选择的 (j) 随着 (i) 的增大一定是还能继续选的。因此我们可以维护一个类似单调队列的东西,在队列中 (2 imes S_j - S_{p_j}) 单调递增,每次我新加入一个元素,从队尾一直开始弹出知道这个 (2 imes S_{q_t} - S_{p_{q_t}} < 2 imes S_j - S_{p_j}) 为止,那么这样我们获得的每个值的位置都将是当前最靠右的点,并且由于 (S_i) 在不断单调递增,我们只需要每次不断移动队尾找到队列中最后一个 (le S_i) 的元素即可。

    dp[1] = a[1] * a[1], h = 1, t = 2, q[1] = 0, q[2] = 1;
    rep(i, 2, n){
        while(h <= t && 2 * sum[q[h]] - sum[p[q[h]]] <= sum[i]) ++h;
        --h, p[i] = q[h];
        dp[i] = dp[p[i]] + (sum[i] - sum[p[i]]) * (sum[i] - sum[p[i]]);
        while(h <= t && 2 * sum[i] - sum[p[i]] <= 2 * sum[q[t]] - sum[p[q[t]]]) --t;
        q[++t] = i;
    }
    printf("%lld", dp[n]);
    

    最后那 (12) 分需要使用高精度,根据我们的流程可以看出实际上我们不需要记录 (dp) 数组,只需要记录之前的 (p) 即可,最后我们只需要不断往下迭代加上每一段的值即可,我的高精太丑了(其实是写的 (\_\_int128))就不贴了。

    GO!
  • 相关阅读:
    vue视图刷新失效
    vue移动端框架搭建
    猜单词--莫妮卡的新游戏
    快速上手python的坑
    江西育华学校初三下月考英语试卷 2020.3
    Title
    什么是vuex? 什么场景下适用vuex?
    左右模块滑动
    vue-router的几种实例方法以及参数传递
    完整的 vue-router 导航解析流程
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13450216.html
Copyright © 2011-2022 走看看