[HEOI2016/TJOI2016]序列(cdq優化dp)
阿新 • • 發佈:2021-10-07
真實情景下應用 cdq 分治優化 dp 初體驗。
給定一個序列 \(a\) 和若干對整數 \(x,y\),表示在一次變化中, \(a_x \gets y\)。這次變化結束後,\(a\)復原。試選出一個子序列,使其在任意一次變化中都滿足單調不降。
LIS 問題作為 dp 入門題十分熟悉,即 \(f_i=\max\{f_j\}+1\),要求 \(j\) 滿足 \(j<i,a_j\le a_i\)。這道題實際上是增加了兩條限制: \(max_j\le a_i,a_j\le min_i\)。這裡的 \(max_x,min_x\) 表示 \(a_x\) 可能變化為的最大、最小值。
觀察這四條限制,不難發現:
- \(a_j\le a_i\) 被包含在新加的兩條限制中,不用單獨考慮;
- \(j<i\) 可以直接轉化為 cdq 分治的時間維!
標準的三維偏序問題,cdq 分治+樹狀陣列處理左邊對右邊的貢獻即可。時間複雜度 \(O(n\log n)\)。
注意:要計算不發生變化時的情況;cdq 時先遞迴左邊,然後計算左對右的貢獻,再遞迴右邊。
下面是 AC 程式碼:
#include <iostream> #include <cstdio> #include <algorithm> using std::max; using std::min; using std::sort; inline int rd() { int x = 0, f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) f ^= (c == '-'); for (; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48); return f ? x : -x; } const int N = 1e5 + 5; const int V = 1e5; int n, a[N], mx[N], mn[N], p[N], f[N], c[N]; #define lowbit(x) (x & (-x)) inline void ins(int x, int y) { for (; x <= V; x += lowbit(x)) c[x] = max(c[x], y); } inline int ask(int x) { int res = 0; for (; x; x -= lowbit(x)) res = max(res, c[x]); return res; } inline void clr(int x) { for (; x <= V; x += lowbit(x)) c[x] = 0; } void cdq(int l, int r) { if (l == r) return f[l] = max(f[l], 1), void(); int mid = (l + r) >> 1; cdq(l, mid); for (int i = l; i <= r; ++i) p[i] = i; sort(p + l, p + mid + 1, [](int x, int y) { return mx[x] < mx[y]; }); sort(p + mid + 1, p + r + 1, [](int x, int y) { return a[x] < a[y]; }); for (int i = mid + 1, j = l; i <= r; ++i) { while (j <= mid && mx[p[j]] <= a[p[i]]) ins(a[p[j]], f[p[j]]), ++j; f[p[i]] = max(f[p[i]], ask(mn[p[i]]) + 1); } for (int i = l; i <= mid; ++i) clr(a[i]); cdq(mid + 1, r); } int main() { n = rd(); int m = rd(), ans = -1; for (int i = 1; i <= n; ++i) a[i] = mx[i] = mn[i] = rd(); for (int i = 1; i <= m; ++i) { int x = rd(), y = rd(); mx[x] = max(mx[x], y); mn[x] = min(mn[x], y); } cdq(1, n); for (int i = 1; i <= n; ++i) ans = max(ans, f[i]); printf("%d\n", ans); return 0; }