Description
给定一个长度为 \(n\) 的整数序列 \(a_1, a_2, \ldots, a_n\)。
你希望把 \(a\) 划分成若干个子序列 \(b_1, b_2, \ldots, b_k\),并满足以下条件:
- \(a\) 中的每个元素恰好属于某个 \(b_i\);
- 对于每个子序列 \(b_i\),设其元素为 \(b_{i,1}, b_{i,2}, \ldots, b_{i,p_i}\)。那么对所有 \(1 \le j < p_i\),都必须满足 \(|b_{i,j} - b_{i,j+1}| = 1\)
请计算,将 \(a\) 划分成满足条件的子序列所需的最小数量。
\(1\leq n\leq 10^6\)。
Solution
首先这题是个典型的最小链覆盖的问题,考虑转成二分图最大匹配的形式:对于每个 \(i\),建立左部点 \(L_i\) 和右部点 \(R_i\)。如果 \(i<j\) 且 \(|a_i-a_j|=1\),则让 \(L_i\) 和 \(R_j\) 连边。答案是 \(n-最大匹配\)。
直接跑匹配显然是不行的,考虑将其转化为贪心。
我们枚举左部点,考虑为每个左部点找到与其匹配的右部点。
由于左部点中值为 \(1\) 的只能匹配 \(2\),而 \(3\) 能匹配 \(2\) 和 \(4\),所以 \(3\) 强于 \(1\),那么一定是先为 \(1\) 找到匹配,再为 \(3\) 找到匹配。
而对于左部点的 \(2\) 和 \(4\) 情况会不太一样,因为 \(2\) 能匹配 \(1\) 和 \(3\),\(4\) 能匹配 \(3\) 和 \(5\),此时 \(2\) 不止能匹配 \(3\)。但是注意到如果存在一个 \(1\) 没有和 \(2\) 匹配,那么这个 \(1\) 就不能被匹配了。所以策略是对于每个 \(2\),如果能匹配 \(1\),就找到离它最近的 \(1\) 匹配,否则就找最近的 \(3\) 匹配。
最终策略就是从小到大枚举左部点的权值 \(v\),如果当前位置 \(x\) 能和值为 \(v-1\) 的右部点匹配,就找最近的 \(v-1\) 匹配,否则找最近的 \(v+1\) 匹配。
时间复杂度:\(O(n\log n)\)。
Code
#include <bits/stdc++.h>// #define int int64_tconst int kMaxN = 1e6 + 5;int n;
int a[kMaxN];
std::vector<int> pos[kMaxN * 2];
std::set<int> st[kMaxN * 2];void dickdreamer() {std::cin >> n;for (int i = 1; i <= 2 * n + 1; ++i) pos[i].clear(), st[i].clear();for (int i = 1; i <= n; ++i) std::cin >> a[i], pos[a[i]].emplace_back(i), st[a[i]].emplace(i);int ans = n;for (int i = 1; i <= 2 * n; ++i) {std::reverse(pos[i].begin(), pos[i].end());for (auto x : pos[i]) {auto it = st[i - 1].lower_bound(x);if (it != st[i - 1].end()) {--ans, st[i - 1].erase(it);} else {it = st[i + 1].lower_bound(x);if (it != st[i + 1].end()) --ans, st[i + 1].erase(it);}}}std::cout << ans << '\n';
}int32_t main() {
#ifdef ORZXKRfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifstd::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;
}