Monotonicity 2(資料加強版)
阿新 • • 發佈:2020-07-26
C. Monotonicity 2(資料加強版)
題目描述
- 對於一個整數序列\(a_1,a_2,...,a_n\),我們定義其“單調序列"為一個由 <,> 和 = 組成的符號序列 \(s_1,s_1,...s_{n-1}\),其中符號 \(s_i\) 表示 \(a_i\) 和 \(a_{i+1}\) 之間的關係。例如,數列 2,4,3,3,5,3 的單調序列為 <,>,=,<,> 。
- 對於整數序列 \(b_1,b_2,...,b_{n+1}\) 以及其單調序列 \(s_1,s_2,..s_n\),如果符號序列 \(s'_1,s'_2,...s'_k\)
- <,>,=
- <,>,=,<,>
- <,>,=,<,>,<,<,=
- <,>,=,<,>,=,>,>
- 給定一個整數序列 \(a_1,a_2,...,a_n\) 以及一個單調序列 \(s_1,s_2,..s_k\),求出原整數序列最長的子序列\(a_{i_1},a_{i_2},...,a_{i_m}(1\le i_1<i_2<...<i_m\le n)\) 使得前者的單調序列實現後者的符號序列。
輸入格式
- 第一行包含用空格分隔的兩個整數 n,k,分別表示整數序列 \(a_i\) 的長度和單調序列 \(s_j\) 的長度。
- 第二行包含用空格分隔的 n 個整數,表示序列 \(a_i\).
- 第三行包含用空格分隔的 k 個符號,表示符號序列 \(s_j\).
輸出格式
- 第一行輸出一個整數 m (保證答案不小於 2),表示序列 \(a_1,a_2,...,a_n\)
- 第二行輸出任意一個這樣的子序列\(a_{i_1},a_{i_2},...,a_{i_m}\),元素之間用空格分隔。
樣例輸入
7 3
2 4 3 1 3 5 3
< > =
樣例輸出
6
2 4 3 3 5 3
資料範圍與提示
- 對於 100% 的資料\(1\le 5\times 10^5,1\le k\le 100,1\le a_i \le 10^6,s_j\in \{<,>,=\}\) 。
Solve
-
可以用樹狀陣列完成的題,為什麼要用線段樹呢?
-
先簡化一下題意:
- 就是將 s 序列複製幾次展開,讓 a 的子序列的符號是 s 序列的字首。
- 就像 <,>,= 可以寫成 <,>,=,<,>,=,<,>,=,... a 序列的一個子序列 2,4,3,3,5,3 的符號序列 <,>,=,<,> 就是上面展開的那個序列的字首,所以合法。
-
根據題目可以想到\(O(n^2)\)的寫法:定義\(f_i\)為以\(a_i\)為結尾的最長合法序列。(有些類似最長上升子序列)
-
第一維列舉狀態 i,第二維選取決策 j,就是在 1 到 i-1 中選取一個 j 使得\(a_i\)可以接在 \(a_j\) 後面且 \(f_j\) 最大。(在類比一下最長上升子序列)
-
這裡考慮\(a_i\)可以接在 \(a_j\) 後面條件,因為\(f_j\)是最長長度,這樣它後面的符號其實就確定了,就是\(s[f_i]\)(預處理是先把 s 序列按我描述的題意展開),是如果要接到\(a_j\)的後面,必須要滿足\(a_j s[f_i]a_i,s[f_i]\in \{<,>,=\}\)
-
\(n^2\)的解法是跑不了n的範圍是\(5\times 10^5\)的資料的,考慮優化。
-
關於DP的優化,什麼單調佇列優化,斜率優化其實都是在選決策 j 的時侯進行優化,這裡的決策也可以進行優化。
-
每次都只有三種情況,而且是選取的最大值,其實決策 j 的取值也有三種:
- \(a_j<a_i\)中 f 值最大的 j
- \(a_j>a_i\)中 f 值最大的 j
- \(a_j=a_i\)中 f 值最大的 j
-
都是取最大值而且小於號情況是字首最大值,大於號情況是字尾最大值,這兩個用開在 0-1e6 樹狀陣列維護,等於號的其實開個陣列就夠了,因為它相當與單點操作,不需要樹狀陣列或線段樹維護
-
關於樹狀陣列如何維護字尾最大值,我們想,維護字首最大值的時候是向後更新,向前查詢,那維護字尾最大值就可以向前更新,向後查詢,其實是一個道理的。
-
我的程式碼也不是很長,還有不理解的地方可以看一看程式碼
Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e5 + 5, M = 1e6 + 5;
char c[N];
int n, k, a[N], f[N], t1[M], t2[M], t[M], p[N], ans, last;
int low(int x) {//lowbit函式
return x & -x;
}
void Change1(int x) {字首最大值向後更新
for (int i = a[x]; i <= 1e6; i += low(i))
if (f[x] > f[t1[i]]) t1[i] = x;
}
int Ask1(int x) {字首最大值向前查詢
int b = 0;
for (int i = a[x] - 1; i; i -= low(i))
if (f[b] < f[t1[i]]) b = t1[i];
return b;
}
void Change2(int x) {字尾最大值向前更新
for (int i = a[x]; i; i -= low(i))
if (f[x] > f[t2[i]]) t2[i] = x;
}
int Ask2(int x) {字尾最大值向後查詢
int b = 0;
for (int i = a[x] + 1; i <= 1e6; i += low(i))
if (f[b] < f[t2[i]]) b = t2[i];
return b;
}
void Print(int x) {//遞迴輸出方案
if (!x) return;
Print(p[x]);
printf("%d ", a[x]);
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]), f[i] = 1;//f初始化為1
for (int i = 1; i <= k; ++i)
scanf(" %c", &c[i]);
for (int i = k + 1; i < n; ++i)
c[i] = c[(i-1)%k+1];//展開
for (int i = 1, j; i <= n; ++i) {
if (f[i] < f[j=Ask1(i)] + 1)//查詢小於號
f[i] = f[j] + 1, p[i] = j;
if (f[i] < f[j=Ask2(i)] + 1)//查詢大於號
f[i] = f[j] + 1, p[i] = j;
if (f[i] < f[j=t[a[i]]] + 1)//查詢等於號
f[i] = f[j] + 1, p[i] = j;
if (ans < f[i]) ans = f[i], last = i;//更新答案
if (c[f[i]] == '<') Change1(i);//更新小於號
if (c[f[i]] == '>') Change2(i);//更新大於號
if (c[f[i]] == '=' && f[i] > f[t[a[i]]]) //更新等於號
t[a[i]] = i;
}
printf("%d\n", ans);
Print(last);//輸出方案
return 0;
}