题解 QOJ2570【Maximal Subsequence】/ SS240121B【LIS】

同时是【PER #1】子序列、QOJ2570 Maximal Subsequence、SS240121B LIS 的题解。

题解 SS240121B【LIS】

http://cplusoj.com/d/master/p/85

题目描述

花花和啾啾是好朋友。花花想送给啾啾一个序列,可是啾啾不喜欢递增子序列(lis)太长的序列。

可惜花花现在只有一个长度为 \(n\) 的序列 \(a_i\)。于是花花不得已只能保留一个 \(a\) 的子序列送给啾啾。为了表达诚意,保留后的序列的 lis 必须比原序列的 lis 小。

花花想问问你,他最长可以保留多长的序列呢?

\(n\leq 10^6,1\leq a_i\leq 10^9\)

solution

网络流 I

  • 分层图。记原序列的 LIS 长度为 \(F\)
  • \(S\)\((i, 1)\) 连边。
  • \((i, F)\)\(T\) 连边。
  • \((i, t)\)\((j, t + 1)\) 连边,当且仅当 \(i<j, a_i<a_j\)
  • \(S\)\(T\) 的最小割,每个点割掉的费用是 \(1\)
  • 理论 \(O(n^{4.5})\) 实际过了 \(n=50\)。计算方法是 \(O(m\sqrt{\sum flow})\) 即边权总和开根号乘边数,听说是,实际不知道。

我们要说明他的正确性。这是基于每个点只会被割一次的观察,因为如果被割了两次你就构造了一个更大的 \(F\)

网络流 II

  • 记,以 \(i\) 为结尾的 LIS 的长度为 \(f_i\)
  • \(S\) 向所有 \(f_i=1\)\(i\) 连边。
  • \(f_i=F\) 的所有 \(i\)\(T\) 连边。
  • \(i\)\(j\) 连边,当且仅当 \(i<j, a_i < a_j, f_j = f_i + 1\)
  • \(S\)\(T\) 的最小割,每个点割掉的费用是 \(1\)

复杂度看似是 \(O(n)\) 个点,\(O(n^2)\) 条边,总共 \(O(n^3)\)。实际上这里有说法,说是因为分层,如果 \(F<\sqrt n\) 那么复杂度是 \(O(n^2\sqrt n)\),否则存在一层点使得这层点有 \(\leq \sqrt n\) 个点,同样是 \(O(n^2\sqrt n)\)

网络流 II 的优化

线段树优化建图。观察到 \(f_i\) 相同的 \(i\),若记为 \(i_1,i_2,\cdots,i_k\),那么一定有 \(a_{i_1}\geq a_{i_2}\geq\cdots\geq a_{i_k}\),否则不满足定义。也就是说,将每一层的都按照下标排序,那么连边一定是一个点向一个区间连边。于是线段树优化建图。复杂度不想算。

模拟网络流

进一步的,我们发现对于同一层的 \(i, j(i<j,f_i=f_j)\),满足它们对应的区间 \([l_1,r_2],[l_2,r_2]\)\(l_1\leq l_2,r_1\leq r_2\)(显然,双指针区间)。考虑一个很劲爆的事情,网络流不需要反悔。

考虑对于一个点 \(u\),我将流量赋予区间内编号最小的点。假设这个流量成功流了下去,找到了一条増广路。然后在决策 \(u\) 同层的旁边的一个编号比他大的点 \(v\)。考虑,\(v\) 是否能将 \(u\) 的决策反悔。一种情况是 \(v\) 碰不到 \(u\) 的决策,那么不用管。即使真的有这么一条增广路来到了 \(u\),发现因为是双指针区间,你是否増广都是合法的。所以就不需要反悔了。这里不需要考虑绕很远绕回来的情况,就在那一层考虑反悔。

不需要反悔的网络流,直接暴力即可。每次去暴力找增广路,找编号最小的过去,然后找到就删掉,不合法也删掉,没用也删掉。\(O(n)\)。结合求 \(f_i\) 这一步,总的复杂度是 \(O(n\log n)\) 的。

code

using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
typedef long long LL;
int n, a[1000010], f[1000010], t[1000010], orz;
vector<int> dts[1000010], vec;
void add(int x, int k) { for (; x <= n; x += x & -x) t[x] = max(t[x], k); }
int query(int x) { int r = 0; for (; x >= 1; x -= x & -x) r = max(r, t[x]); return r; }
bool dfs(int d) {
  if (dts[d].empty()) return false;
  if (d == orz) return true;
  int u = dts[d].back();
  while (!dts[d + 1].empty()) {
    int v = dts[d + 1].back();
    if (u > v) break;
    if (u < v && a[u] < a[v] && dfs(d + 1)) return true;
    else dts[d + 1].pop_back();
  }
  return false;
}
int main() {
#ifndef LOCAL
  freopen("lis.in", "r", stdin);
  freopen("lis.out", "w", stdout);
  cin.tie(nullptr)->sync_with_stdio(false);
#endif  
  cin >> n;
  for (int i = 1; i <= n; i++) cin >> a[i], vec.push_back(a[i]);
  sort(vec.begin(), vec.end());
  vec.erase(unique(vec.begin(), vec.end()), vec.end());
  for (int i = 1; i <= n; i++) {
    a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin() + 1;
    add(a[i], f[i] = query(a[i] - 1) + 1);
    dts[f[i]].push_back(i);
    debug("a[%d] = %d, f[%d] = %d\n", i, a[i], i, f[i]);
  }
  orz = query(vec.size());
  int flw = 0;
  while (!dts[1].empty()) {
    if (!dfs(1)) { dts[1].pop_back(); continue; }
    ++flw;
    debug("++flw\n");
    for (int i = 1; i <= orz; i++) dts[i].pop_back();
#ifdef LOCAL
    for (int i = 1; i <= orz; i++) {
      for (int x: dts[i]) debug("%d, ", x);
      debug("\n");
    }
#endif
  }
  cout << n - flw << endl;
  return 0;
}
posted @ 2024-01-21 19:18  caijianhong  阅读(21)  评论(0)    收藏  举报