Description
将一个长度为 m 的数组 b 称为 好数组(good),如果它满足以下两个条件:
对于每个 1≤i≤m,都有 1≤bi≤i。
存在一个长度为 m 的排列 p,使得对于每个 1≤i≤m,都有:
bi 是使得区间 [bi,i] 中的最小值恰好等于 pi 的最小下标,也就是满足
min(pbi,pbi+1,…,pi)=pi
例如,数组 [1,1,3,3,5] 是一个好数组(可以取排列 p=[2,1,5,3,4] 来满足第二个条件);
而数组 [1,1,2] 不是好数组。
注意:空数组被视为好数组。
现在给定一个长度为 n 的数组 a,你需要找出其中最长的好子序列的长度。
n≤15000。
Solution
考虑什么样的数组 b 是合法的。
显然需要满足 bbi−1 是 bi 左边第一个比 bi 小的数,那么由于 bbi≥bi>bbi−1,所以 bbi=bi。
考虑对 bx=x 的这些位置进行区间 dp。
具体地,设 fi,j 表示只考虑 [i,j] 中大于等于 ai 的数,i 必须选,且 i 是第 ai 个选的数的最长长度。注意这里由于已经钦定 i 的位置了,所以就不管前面是否能选以及 ai 这里是否满足条件,把这个区间内的数看成一个独立的个体。
有如下几种转移:
aj<ai,则 fi,j←fi,j−1。
aj=ai,则 fi,j←fi,j−1+1。
aj>ai,现在需要为 aj 找到其对应的开头 k,满足 ak=aj,且 k 可以作为第 ak 个数出现。
这个显然等价于 fi,k−1≥ak−1,因为由于这个子序列支持从末尾删除,所以可以得到一个长度为 ak−1 的子序列,而 bi≤i,所以前面的数都小于等于 ak−1,此时 ak 就可以作为开头了。
转移为 fi,j←max{fi,j−1,fk,j},由于只需要找到最小的满足条件的 k,扫右端点的时候维护一个数组即可。
答案即为所有满足 ai=1 的 i 中 fi,n 的最大值。
时间复杂度:O(n2)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <bits/stdc++.h>
const int kMaxN = 1.5e4 + 5;
int n; int a[kMaxN], f[kMaxN][kMaxN];
void chkmax(int &x, int y) { x = (x > y ? x : y); } void chkmin(int &x, int y) { x = (x < y ? x : y); }
void dickdreamer() { std::cin >> n; for (int i = 1; i <= n; ++i) std::cin >> a[i]; int ans = 0; for (int i = n; i; --i) { static int fir[kMaxN]; std::fill_n(fir + 1, n, 0); f[i][i] = a[i]; for (int j = i + 1; j <= n; ++j) { f[i][j] = f[i][j - 1]; if (a[j] < a[i]) continue; if (a[j] == a[i]) chkmax(f[i][j], f[i][j - 1] + 1); if (!fir[a[j]] && f[i][j - 1] + 1 >= a[j]) fir[a[j]] = j; if (fir[a[j]]) chkmax(f[i][j], f[fir[a[j]]][j]); } if (a[i] == 1) chkmax(ans, f[i][n]); } std::cout << ans << '\n'; }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; std::cin >> T; while (T--) dickdreamer(); return 0; }
|