zoukankan      html  css  js  c++  java
  • Monotonicity 2(数据加强版)——数状数组+线性DP

    C. Monotonicity 2(数据加强版)

    题目描述

    • 对于一个整数序列(a_1,a_2,...,a_n),我们定义其“单调序列"为一个由 <,> 和 = 组成的符号序列 (s_1,s_1,...s_{n-1}),其中符号 (s_i) 表示 (a_i)(a_{i+1}) 之间的关系。例如,数列 2,4,3,3,5,3 的单调序列为 <,>,=,<,> 。
    • 对于整数序列 (b_1,b_2,...,b_{n+1}) 以及其单调序列 (s_1,s_2,..s_n),如果符号序列 (s'_1,s'_2,...s'_k) 满足对所有 (1le ile n)(s_i=s'_{((i-1)mod k)+1}),我们就说序列 (s_1,s_2,...,s_n)「实现」了序列 (s'_1,s'_2,...,s'_k)。也就是说,序列 (s_1,s_1,...s_n) 可以通过重复多次 (s'_1,s'_2,...,s'_k) 序列并删除一个后缀得到。例如,整数数列 2,4,3,3,5,3 至少实现了以下符号序列:
      • <,>,=
      • <,>,=,<,>
      • <,>,=,<,>,<,<,=
      • <,>,=,<,>,=,>,>
    • 给定一个整数序列 (a_1,a_2,...,a_n) 以及一个单调序列 (s_1,s_2,..s_k),求出原整数序列最长的子序列(a_{i_1},a_{i_2},...,a_{i_m}(1le i_1<i_2<...<i_mle n)) 使得前者的单调序列实现后者的符号序列。

    输入格式

    • 第一行包含用空格分隔的两个整数 n,k,分别表示整数序列 (a_i) 的长度和单调序列 (s_j) 的长度。
    • 第二行包含用空格分隔的 n 个整数,表示序列 (a_i).
    • 第三行包含用空格分隔的 k 个符号,表示符号序列 (s_j).

    输出格式

    • 第一行输出一个整数 m (保证答案不小于 2),表示序列 (a_1,a_2,...,a_n) 的最长的「实现」了单调序列 (s_1,s_2,..s_n) 的子序列。
    • 第二行输出任意一个这样的子序列(a_{i_1},a_{i_2},...,a_{i_m}),元素之间用空格分隔。

    样例输入

    7 3
    2 4 3 1 3 5 3
    < > =
    

    样例输出

    6
    2 4 3 3 5 3
    

    数据范围与提示

    • 对于 100% 的数据(1le 5 imes 10^5,1le kle 100,1le a_i le 10^6,s_jin {<,>,=})

    Solve

    • 可以用树状数组完成的题,为什么要用线段树呢?

    • 先简化一下题意:

      • 就是将 s 序列复制几次展开,让 a 的子序列的符号是 s 序列的前缀。
      • 就像 <,>,= 可以写成 <,>,=,<,>,=,<,>,=,... a 序列的一个子序列 2,4,3,3,5,3 的符号序列 <,>,=,<,> 就是上面展开的那个序列的前缀,所以合法。
    • 根据题目可以想到(O(n^2))的写法:定义(f_i)为以(a_i)为结尾的最长合法序列。(有些类似最长上升子序列)

    • 第一维枚举状态 i,第二维选取决策 j,就是在 1 到 i-1 中选取一个 j 使得(a_i)可以接在 (a_j) 后面且 (f_j) 最大。(在类比一下最长上升子序列)

    • 这里考虑(a_i)可以接在 (a_j) 后面条件,因为(f_j)是最长长度,这样它后面的符号其实就确定了,就是(s[f_i])(预处理是先把 s 序列按我描述的题意展开),是如果要接到(a_j)的后面,必须要满足(a_j s[f_i]a_i,s[f_i]in {<,>,=})

    • (n^2)的解法是跑不了n的范围是(5 imes 10^5)的数据的,考虑优化。

    • 关于DP的优化,什么单调队列优化,斜率优化其实都是在选决策 j 的时侯进行优化,这里的决策也可以进行优化。

    • 每次都只有三种情况,而且是选取的最大值,其实决策 j 的取值也有三种:

      1. (a_j<a_i)中 f 值最大的 j
      2. (a_j>a_i)中 f 值最大的 j
      3. (a_j=a_i)中 f 值最大的 j
    • 都是取最大值而且小于号情况是前缀最大值,大于号情况是后缀最大值,这两个用开在 0-1e6 树状数组维护,等于号的其实开个数组就够了,因为它相当与单点操作,不需要树状数组或线段树维护

    • 关于树状数组如何维护后缀最大值,我们想,维护前缀最大值的时候是向后更新,向前查询,那维护后缀最大值就可以向前更新,向后查询,其实是一个道理的。

    • 我的代码也不是很长,还有不理解的地方可以看一看代码

    Code

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N = 5e5 + 5, M = 1e6 + 5;
    char c[N];
    int n, k, a[N], f[N], t1[M], t2[M], t[M], p[N], ans, last;
    int low(int x) {//lowbit函数
        return x & -x;
    }
    void Change1(int x) {//前缀最大值向后更新
        for (int i = a[x]; i <= 1e6; i += low(i))
            if (f[x] > f[t1[i]]) t1[i] = x;
    }
    int Ask1(int x) {//前缀最大值向前查询
        int b = 0;
        for (int i = a[x] - 1; i; i -= low(i))
            if (f[b] < f[t1[i]]) b = t1[i];
        return b;
    }
    void Change2(int x) {//后缀最大值向前更新
        for (int i = a[x]; i; i -= low(i))
            if (f[x] > f[t2[i]]) t2[i] = x;
    }
    int Ask2(int x) {//后缀最大值向后查询
        int b = 0;
        for (int i = a[x] + 1; i <= 1e6; i += low(i))
            if (f[b] < f[t2[i]]) b = t2[i];
        return b;
    }
    void Print(int x) {//递归输出方案
        if (!x) return;
        Print(p[x]);
        printf("%d ", a[x]);
    }
    int main() {
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; ++i)
             scanf("%d", &a[i]), f[i] = 1;//f初始化为1
        for (int i = 1; i <= k; ++i)
            scanf(" %c", &c[i]);
        for (int i = k + 1; i < n; ++i)
            c[i] = c[(i-1)%k+1];//展开
        for (int i = 1, j; i <= n; ++i) {
            if (f[i] < f[j=Ask1(i)] + 1)//查询小于号
                f[i] = f[j] + 1, p[i] = j;
            if (f[i] < f[j=Ask2(i)] + 1)//查询大于号
                f[i] = f[j] + 1, p[i] = j;
            if (f[i] < f[j=t[a[i]]] + 1)//查询等于号
                f[i] = f[j] + 1, p[i] = j;
            if (ans < f[i]) ans = f[i], last = i;//更新答案
            if (c[f[i]] == '<') Change1(i);//更新小于号
            if (c[f[i]] == '>') Change2(i);//更新大于号
            if (c[f[i]] == '=' && f[i] > f[t[a[i]]]) //更新等于号
                t[a[i]] = i;
        }
        printf("%d
    ", ans);
        Print(last);//输出方案
        return 0;
    }
    
  • 相关阅读:
    python的模块future用法实例解析
    strcmp函数和memcmp函数的用法区别及联系
    esp8266 smartconfig-智能配网分析和使用及注意事项
    ubuntu 18.04 安装并配置adb
    Markdown的常用方法总结
    mac下使用minicom几个注意事项
    最强Linux shell工具Oh My Zsh 指南
    ESP8266源码分析--打印的基本用法
    atom 在Ubuntu 18.04 上安装及基本使用
    ubuntu 查看端口被占用并删除端口
  • 原文地址:https://www.cnblogs.com/shawk/p/13379107.html
Copyright © 2011-2022 走看看