zoukankan      html  css  js  c++  java
  • 题解 P6563 【[SBCOI2020] 一直在你身旁】

    (Large exttt{P6563})

    算法:单调队列优化DP

    题意

    不做赘述。

    但是可以想象成一个双方玩博弈的过程, A 取当前区间的一个数(位置为 (i) ((l le i < r)))花 (a_i) ,将这个区间分成 ([l,i])([i+1,r]) ,B根据计算选两个区间中一个区间使 A 最后花更多的钱,而 A 要花更少的钱。

    注意 (a_i) 非严格单调递增。

    思路

    • 20pts

    可以很容易的想到一个 (O(N^3)) 的区间DP:

    [f[i][j]=min_{k=l}^{k<r}(max(f[i][k],f[k+1][j])+a_k) ]

    (min) 代表 A 主观选最小的, (max) 为 B 主观选两个区间中更大价值的区间。

    其中 (f[i][i+1]=a_i)(f[i][i]=0)

    • 100pts

    考虑优化,首先总有 (f[i][j]le f[i][j+1]) (显然,但不好证QwQ) (a_i) 是非严格单调递增的,那么在这个式子送,如果能枚举一个下标,将 (f[i][j]) 变成一个在 (i) (j) 意义下的一维数组,既然是单调的是否可以考虑单调队列

    但是这个 (max) 操作有点限制我们。

    但因为f有单调性,我们知道随着 (k) 变大 (f[i][k]) 变大, (f[k+1][j]) 变小,也就是说我们可以找到一个点 (p) 对于 ((i,j)) 来说 (f[i][k]) 恰好大于 (f[k+1][j])(k) 点,而 (p) 右边 (max) 的值选后者, (p) 左边选前者。

    联系第一段话,我们现在有两种选择:

    1. 固定(优先枚举) (i) ,再枚举 (j)
    2. 固定(优先枚举) (j) ,再枚举 (i)

    在枚举第二关键字的过程中 (p) 点应是可从上一个 (p) 点单向移动得到的,具过程体可以手玩得到。

    后将 ([i,j]) 分为 ([p,j])([i,p-1]) 分别考虑每个点的贡献:

    1. ([p,j]) 贡献为 (min{f[i][k]+a_k}) 因为随着 (k) 的增加 (a_k)(f[i][k]) 单调递增,显然的贡献为 (f[i][p]+a_p)
    2. ([1,p-1]) 贡献为 (min{f[k][j]+a_k}) ,然而这里就有问题了, (a_k)(f[k][j]) 的单调方向相反,难搞。

    对于第二类情况,(呼应开头)因为变量只有两个,考虑固定其中一个,再枚举另一个,用单调队列维护。

    所以只能固定 (j) (我因为这个搞不懂弄了半天),枚举 (i)

    至此问题基本解决。

    反思

    重点:

    • 要能想到 (f[i][j]le f[i][j+1]) ,别老是对一个解释不清但显然成立的东西要证明,有些东西就是难以解释。
    • 必须将 (j) 作为第一关键字,这是罕见的一道题吧,挺神的。
    • 要顺序从 (1) 开始枚举 (j) ,倒序从 (j) 开始枚举 (i) (其他方法都不行)。

    最好自己手推一下,可以参考下代码。

    代码

    主体挺短的,但是写起来真的很难受。

    int t, a, s[N + 10], f[N + 10][N + 10], mid[N + 10], l, r, qu[N + 10]; //(mid就是文中的p)
    
    signed main()
    {
        // freopen("in1.in", "r", stdin);
        t = read();
        while (t--)
        {
            a = read();
            for (int i = 0; i <= a + 1; i++)
                for (int j = 0; j <= a + 1; j++)
                    f[i][j] = 1e18;
            for (int i = 1; i <= a; i++)
                s[i] = read();
            for (int i = 1; i <= a; i++)
                f[i][i] = 0, f[i][i + 1] = s[i];
            for (int j = 3; j <= a; j++)
            {
                mid[j - 1] = j - 1;
                l = 1;
                r = 0;
                for (int i = j - 2; i >= 1; i--)
                {
                    mid[i] = mid[i + 1];
                    while (mid[i] > i && f[i][mid[i] - 1] > f[mid[i]][j])
                        mid[i]--;
                    while (l <= r && qu[l] >= mid[i])
                        l++;
                    f[i][j] = min(f[i][j], f[i][mid[i]] + s[mid[i]]);
                    if (l <= r)
                        f[i][j] = min(f[i][j], f[qu[l] + 1][j] + s[qu[l]]);
                    while (l <= r && f[i + 1][j] + s[i] < f[qu[r] + 1][j] + s[qu[r]])
                        r--;
                    qu[++r] = i;
                }
            }
            printf("%lld
    ", f[1][a]);
        }
        return 0;
    }
    
  • 相关阅读:
    向量期望与方差的关系
    向量范数的柯西不等式
    pandas sample函数
    latex 插入表格的网址
    寻找原根
    反射
    python库下载速度慢的解决
    anaconda3 修改python版本
    spark RDD的缓存和检查点不同使用场景和工作机制 -- (视频笔记)
    HTML一键打包APK解决误报毒问题
  • 原文地址:https://www.cnblogs.com/RedreamMer/p/14369444.html
Copyright © 2011-2022 走看看