题解 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;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/17978195/solution-ss240121b
浙公网安备 33010602011771号