• [luogu p3143] [USACO16OPEN]Diamond Collector S


    (mathtt{Link})

    传送门

    (mathtt{Summarization})

    给定一个非严格递增序列,找到两段长度和最大,且不交的子序列,并且使得每个子序列都满足极差不严格大于给定参数 (k)。(程序要求输出长度和,不要求输出子序列)

    (mathtt{Solution})

    你可能有一个问题想问,为什么是给定一个非严格递增序列?题目中明明是乱序随便给的啊。其实你会发现,钻石的顺序并不影响答案,但是将钻石排序可以将题目的“随便抽着放”转化为“连续子序列”。

    其实很好理解,因为如果价值是 (a) 的钻石和价值是 (b(a<b)) 的钻石能放在一个架子上,那么价值为 (k) 且满足 (a le k le b) 的钻石一定能放到这个架子上。因此排序后,如果两个钻石能放在架子上,那么中间的全能放。显然中间只要有一个不放就比中间全放的答案劣,因此要抽我们就要抽一个连续子序列。

    懒得加粗提示了,自选重点理解吧(

    理解完后我们继续:

    既然题目要求子序列不可相交,前者的子序列和后者的子序列中间一定有一个分割线,我们规定这个分割线不能在后面那个序列的首,但是可以在前面那个序列的尾

    采用dp思想。

    定义两个数组 (l, r)

    • (l_i) 表示第 (i) 个数及其左边的数中,满足极差不严格大于 (k) 的最大子序列的长度。
    • (r_i) 表示第 (i) 个数及其右边的数中,满足极差不严格大于 (k) 的最大子序列的长度。

    那么最终答案显然就是 (operatorname{max}^{n-1}_{i=1}l_i+r_{i+1})。问题是怎么求 (l)(r)

    我们可以运用一种指针思想。我们以 (l) 数组举例吧,(r) 只是换汤不换药罢了。

    首先先设初始值:(l_1 = 1)(自己成序列)。

    设置两个指针 (i)(j)(i)(2 ightarrow n),而 (j) 不断从序列首更新,每次 (i) 变化时,(j) 就不断自增,直到 (a_i - a_j > k)。此时 (i sim j) 区间长度 和 (l_{i-1}) 的最大值就是 (l_i)。(非常具有dp的味道!)

    同理求 (r_i),最后求 (operatorname{max}^{n-1}_{i=1}l_i+r_{i+1})

    核心算法时间复杂度是 (mathcal{O}(n)),但是排序的时间复杂度已经达到了 (mathcal{O}(nlog n)),因此整个代码的时间复杂度是 (mathcal{O}(nlog n))

    (mathtt{Code})

    /*
     * @Author: crab-in-the-northeast 
     * @Date: 2020-10-21 22:39:05 
     * @Last Modified by: crab-in-the-northeast
     * @Last Modified time: 2020-10-21 22:47:17
     */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    
    const int maxn = 50005;
    int a[maxn], l[maxn], r[maxn];
    
    int max(int a, int b) {
        return a > b ? a : b;
    }
    
    int main() {
        int n, k;
        std :: scanf("%d %d", &n, &k);
        for (int i = 1; i <= n; ++i)
            std :: scanf("%d", &a[i]);
    
        l[1] = r[n] = 1;
        std :: sort(a + 1, a + n + 1);
        for (int i = 2, j = 1; i <= n; ++i) {
            while (a[i] - a[j] > k)
                ++j;
            l[i] = max(l[i - 1], i - j + 1);
        }
    
        for (int i = n - 1, j = n; i >= 1; --i) {
            while (a[j] - a[i] > k)
                --j;
            r[i] = max(r[i + 1], j - i + 1);
        }
    
        int ans = 0;
        for (int i = 1; i <= n; ++i)
            ans = max(ans, l[i] + r[i + 1]);
        std :: printf("%d
    ", ans);
        return 0;
    }
    

    (mathtt{More})

    此题运用了双指针思想,i在前面跑,j在后边追,而整个序列又是单调性,因此时间复杂度就能大大简化。你看本题,核心双指针时间复杂度甚至比前置工作的复杂度还要低!

    所以如果i在前面跑,保证j不会以后退的方式更“可行”,那么就可以使用双指针了。

  • 相关阅读:
    axis2学习笔记
    一个奇怪的数组越界报错
    zk实现分布式锁
    springBoot配置双数据源
    oracle查询
    maven项目打包
    linux修改yum源为阿里云
    kafka入门
    大话设计模式读书笔记(中介者模式)
    大话设计模式读书笔记(职责链模式)
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p3143.html
Copyright © 2020-2023  润新知