首页 > 其他分享 >CF568E Longest Increasing Subsequence 题解

CF568E Longest Increasing Subsequence 题解

时间:2024-08-06 08:55:00浏览次数:10  
标签:pre std int 题解 len Subsequence kMaxN LIS Increasing

Description

给定一个长度为 \(n\) 的有 \(k\) 个空缺的序列。

你有 \(m\) 个数可以用于填补空缺。

要求最大化最长上升子序列的长度。

\(n, m \le 10^5\),\(k \le 10^3\)。

Solution

容易发现只需要先构造出 LIS 上的位置的值,对于其余未填位置随便填,所以构造 LIS 时就不需要考虑出现重复的问题。

考虑先求出最长上升子序列长度。

有一个显然的做法是维护一个数据结构,对于已经填了的位置就正常转移,对于没填的位置暴力枚举填的数然后转移,可以做到 \(O\left((n+m)k\right)\),但是常数会比较大。

有另一个求 LIS 的方法是维护 \(f_i\) 表示当前长度为 \(i\) 的上升子序列的最小末尾值,\(g_i\) 表示这个末尾的位置。每次加入一个 \(x\),就二分求出 \(f_j<x\) 的最大的 \(j\),说明以 \(x\) 结尾的 LIS 长度为 \(j+1\)。又由于 \(f_{j+1}\geq x\),就用 \(x\) 更新 \(f_{j+1}\) 。对于没填的位置可以维护一个指针,从大往小枚举填的数同时更新指针即可。这样做常数就很小了。

然后考虑构造方案。

先在求 LIS 的过程中求出 \(len_i\) 表示以 \(i\) 结尾的 LIS 长度,\(pre_i\) 表示以 \(i\) 结尾的 LIS 中 \(i\) 的前驱的位置。这时会发现对于 \(a_i=-1\) 的位置这两个东西是维护不了的,构造方案时需要特殊处理。

为了方便构造方案,先在数组末尾加入 \(+\infty\)。然后找到 LIS 的末尾位置 \(n+1\),每次往前跳,可以在跳的过程中更新 \(-1\) 的位置的 \(len\) 值。设当前在 \(i\),如果 \(a_i\) 初始不为 \(-1\),有两种情况:

  1. \(a_{pre_i}\) 为 \(-1\),就让 \(a_{pre_i}\) 填 \(<a_i\) 的最大可填值。
  2. \(a_{pre_i}\) 不为 \(-1\),就不用管。

然后就是 \(a_i\) 初始为 \(-1\)(由于是从后往前构造的,所以 \(a_i\) 此时已经填了数了),如果前面存在一个位置 \(j\),使得 \(a_j\neq -1,a_j<a_i,len_j=len_i-1\),那么 \(i\) 的前驱就为 \(j\)。否则一定是一个 \(-1\) 的位置,这里贪心选取最靠近 \(i\) 的那个 \(-1\),同时填上 \(<a_i\) 的最大可填值。填完往前跳即可。

时间复杂度:\(O\left((n+m)k\right)\)。

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5;

int n, m;
int a[kMaxN], b[kMaxN], pre[kMaxN], len[kMaxN], f[kMaxN], g[kMaxN];
bool vis[kMaxN];

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  std::cin >> m;
  for (int i = 1; i <= m; ++i) std::cin >> b[i];
  std::sort(b + 1, b + 1 + m);
  a[n + 1] = 1e9 + 1;
  memset(f, 0x3f, sizeof(f)), memset(pre, -1, sizeof(pre));
  f[0] = g[0] = 0;
  for (int i = 1; i <= n + 1; ++i) {
    if (~a[i]) {
      int j = std::lower_bound(f, f + 2 + n, a[i]) - f - 1;
      len[i] = j + 1, pre[i] = g[j], f[j + 1] = a[i], g[j + 1] = i;
    } else {
      for (int j = m, k = n + 1; j; --j) {
        for (; ~k && f[k] >= b[j]; --k) {}
        f[k + 1] = b[j], g[k + 1] = i;
      }
    }
  }
  for (int i = n + 1; len[i] > 1;) {
    assert(~a[i]);
    if (!~pre[i]) {
      for (int j = i - 1; j; --j) {
        if (~a[j] && len[j] == len[i] - 1 && a[j] < a[i]) {
          pre[i] = j; break;
        }
      }
      if (!~pre[i]) {
        for (int j = i - 1; j; --j) {
          if (!~a[j]) {
            pre[i] = j; break;
          }
        }
      }
    }
    if (!~a[pre[i]]) {
      int j = std::lower_bound(b + 1, b + 1 + m, a[i]) - b - 1;
      a[pre[i]] = b[j], vis[j] = 1, len[pre[i]] = len[i] - 1;
    }
    i = pre[i];
  }
  for (int i = 1, j = 1; i <= n; ++i) {
    if (!~a[i]) {
      for (; vis[j]; ++j) {}
      a[i] = b[j], vis[j] = 1;
    }
  }
  for (int i = 1; i <= n; ++i) std::cout << a[i] << ' ';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

标签:pre,std,int,题解,len,Subsequence,kMaxN,LIS,Increasing
From: https://www.cnblogs.com/Scarab/p/18344424

相关文章

  • 题解 P6873 [COCI2013-2014#6] FONT
    link题意给你\(N\)个单词,问最多能组成多少个包含所有小写英文字母的句子。\(\mathrm{Solution}\)\(N\le25\)显然搜索。枚举当前选还是不选,搜到头判断是否成功即可。\(\mathrm{Code}\)#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;consti......
  • [COCI2015-2016#3] NEKAMELEONI 题解
    前言题目链接:LOJ;洛谷。题意简述在二叉树上,不断删除叶子,你要维护其树链剖分后重儿子编号和。如果两个孩子大小相同,在一开始连向左儿子,或者保持修改前的连接。\(n\leq2\times10^5\)。题目分析有分块的、有二分的,那我来讲一讲我的想法——树剖维护树剖。首先反转操作,不断......
  • Codeforces Round 963 (Div. 2) A - C 详细题解(思路加代码,C++,Python) -- 来自灰名
    比赛链接:Dashboard-CodeforcesRound963(Div.2)-Codeforces之后有实力了再试试后面的题目,现在要做那些题,起码要理解一个多小时题目A:链接:Problem-A-Codeforces题目大意理解:        极少数不考翻译能读懂的cf题目(bushi)每个测试用例第一行一个n,......
  • P4604 [WC2017] 挑战 题解
    题目描述任务一给定\(n\)个\(32\)位无符号整数,将它们从小到大排序。任务二有\(2n\)个人玩"石头剪刀布"游戏,他们分成两排,每排\(n\)个人,\(a_{i,j}=0/1/2\)分别表示第\(i\)排第\(j\)人出石头、剪刀、布。\(q\)次询问,每次给定\(x,y,l\),询问第一排第\(x\simx......
  • CF228E 题解
    CF228E题解题目简述给定一个\(n\)个点,\(m\)条边的无向图,每条边都为\(0\)或\(1\),可以进行若干次操作,与此点相连的所有点权值取反,求一种方案使得所有边都变为\(1\)。前置知识二分图二分图染色思路简述首先明白一点:对于同一条边,操作偶数次是没有必要的!因为最终会回......
  • [Violet 6]故乡的梦 题解
    前言题目链接:Hydro&bzoj。题意简述无向联通图给出起点终点,多次询问删边后最短路,或报告不连通。\(n,m,q\leq2\times10^5\)。题目分析首先想到,如果删掉的边不是原来最短路的边,那么最短路不会发生变化。因此我们只需考虑删除了原来在最短路上的边。不妨把原先最短路任......
  • 洛谷-P9830 题解
    思路分析分析样例:见红线,长宽各为2,存在格点;黄线长2宽3,没有格点。考虑延长黄线使得长4宽6,发现有格点。思考格点,如果长和宽都可以被分成\(p\timesl\)的格式,则存在格点。那么,就能想出:推论1:对于\((0\,\0)\)和\((x\,\y)\)之间没有格点,当且仅当\(\gcd(x\,......
  • P9596 冒泡排序 2 题解
    题目链接。Statement记\(f(A)\)为序列\(A\)的冒泡排序趟数,操作:单点改,全局查\(f(A)\).\(n,m\le5\cdot10^5\),值域1e9.Solution结论:\[Ans=\max_{i\in[1..n]}\left\{\sum_{j\in[1..i]}[A_j>A_i]\right\}\]怎么观察出来啊QAQ证明:对于每个位置\(p\),观察到每趟都......
  • AGC046C 题解
    blog。好菜啊,不会这题,来写个题解/kel。很难直接做,先找一点性质:操作只改变相对顺序,而总数不变。这启示我们记录每个\(0\)前面的极长\(1\)连续段长度。记第\(i(1\lei\leC)\)个\(0\)对应长度为\(a_i\),就存在下面的等价表述:每次操作可以选定\(i,j(1\lei<j\leC)\),......
  • 洛谷-P9574 题解
    思路分析分析样例:==TTBTBTTBTBTTTBTTB->TTBTTBBTTBTTTBTTB->TTBTTBTBBTTTTBTTB->TTBTTBTBBTTTTTBBT----1-----2-----3---4--观察区块2,发现BTTB进行操作后与右边的TB再次构成BTTB,我们发现在这个区块内,可以从左向右不断操作,我们称这种特性为传递性,可......