原题链接
形式化题意
给n个序列,求n个序列任意排列,从前往后遍历遇到的比之前遇到所有数都大的数的个数最多,输出个数。
solution
首先肯定将每一个序列无用的部分去掉。如 [ 1 , 1 , 3 , 2 , 5 , 7 ] [1, 1, 3, 2, 5, 7] [1,1,3,2,5,7]只保留 [ 1 , 3 , 5 , 7 ] [1, 3, 5, 7] [1,3,5,7],因为显然其余部分对答案没有贡献。具体开一个 v e c t o r vector vector实现就好了
code:
int n; cin >> n;
for (int i = 1; i <= n; i++) {
int k; cin >> k;
for (int j = 1; j <= k; j++) {
int x; cin >> x;
if (j == 1 || x > a[i].back()) {
a[i].push_back(x);
}
}
}
然后两个序列可以叠一起组成新的序列,因此肯定是可以dp的。
但是这题最难的点是对“有后效性”的处理。
很显然如果考虑每个序列遍历肯定是要假掉的,因为会影响后面的序列。然后我们注意到 ∑ k i ≤ 200000 \sum k_i \le200000 ∑ki≤200000,同时发现如果一些情况下每个序列只保留后缀与保留整个序列是等效的,例如: [ 1 , 2 , 3 ] [1,2,3] [1,2,3] 和 [ 2 , 3 , 4 , 6 ] [2,3,4,6] [2,3,4,6]中如果把 [ 2 , 3 , 4 ] [2,3,4] [2,3,4]替换为 [ 4 , 6 ] [4,6] [4,6],结果都是 [ 1 , 2 , 3 , 4 , 6 ] [1,2,3,4,6] [1,2,3,4,6],是一样的。因此我们可以把每个序列都变成后缀,这样就可以表示出每一个状态。
(ps.实际上只用记录左右端点和该后缀长度,不用记录整个后缀)
但是这样子还是会有后效性,因为如果左端点不是升序的话在后面的更小的左端点的后缀接到前面肯定是不优的。比如 [ 3 , 4 , 5 ] [3,4,5] [3,4,5]和 [ 1 , 2 ] [1,2] [1,2],肯定是前者接到后者的后面更优,因此我们还要将所有的后缀按左端点升序排序,这样就没有后效性了。
dp方程是好列的:
f i = max j i − 1 , r [ j ] < l [ i ] ( l e n i + f j ) f_i=\max\limits_{j}^{i-1,r[j]<l[i]} (len_i+f_j) fi=jmaxi−1,r[j]<l[i](leni+fj)
然后复杂度是 O ( n 2 ) O(n^2) O(n2),明显超的,但是这题的优化十分简单,只用套一个线段树,应用区间修改和维护最大值,就能降到 O ( n l o g n ) O(nlogn) O(nlogn)
AC Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200500;
vector<int> a[N];
int f[N], tr[4 * N], tag[4 * N];
struct node {
int l, r, len;
} w[N];
int cnt = 0, ans = 0;
inline bool cmp(node a, node b) {
if (a.l != b.l) return a.l < b.l;
return a.r < b.r;
}
inline void pd(int p, int pl, int pr) {
if (tag[p]) {
int mid = (pl + pr) >> 1;
tag[p << 1] = max(tag[p << 1], tag[p]);
tag[p << 1 | 1] = max(tag[p << 1 | 1], tag[p]);
tr[p << 1] = max(tr[p << 1], tag[p]);
tr[p << 1 | 1] = max(tr[p << 1 | 1], tag[p]);
tag[p] = 0;
}
}
void update(int p, int pl, int pr, int l, int r, int v) {
if (l <= pl && pr <= r) {
tr[p] = max(tr[p], v);
tag[p] = max(tag[p], v);
return;
}
pd(p, pl, pr);
int mid = (pl + pr) >> 1;
if (l <= mid) update(p << 1, pl, mid, l, r, v);
if (r > mid) update(p << 1 | 1, mid + 1, pr, l, r, v);
tr[p] = max(tr[p << 1], tr[p << 1 | 1]);
}
int query(int p, int pl, int pr, int l, int r) {
if (l <= pl && pr <= r) {
return tr[p];
}
pd(p, pl, pr);
int mid = (pl + pr) >> 1;
int res = 0;
if (l <= mid) res = max(res, query(p << 1, pl, mid, l, r));
if (r > mid) res = max(res, query(p << 1 | 1, mid + 1, pr, l, r));
return res;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n; cin >> n;
for (int i = 1; i <= n; i++) {
int k; cin >> k;
for (int j = 1; j <= k; j++) {
int x; cin >> x;
if (j == 1 || x > a[i].back()) {
a[i].push_back(x);
}
}
}
for (int i = 1; i <= n; i++) {
int len = a[i].size();
for (int v : a[i]) {
w[++cnt].l = v;
w[cnt].r = a[i].back();
w[cnt].len = len--;
}
}
sort(w + 1, w + cnt + 1, cmp);
for (int i = 1; i <= cnt; i++) {
f[i] = max(w[i].len, f[i - 1]);
if (w[i].l <= 1) {
update(1, 1, N, w[i].r, N, f[i]);
ans = max(ans, f[i]);
continue;
}
int q = query(1, 1, N, 1, w[i].l - 1);
f[i] = q + w[i].len;
update(1, 1, N, w[i].r, N, f[i]);
ans = max(ans, f[i]);
}
cout << ans;
return 0;
}
163





