E. Music Festival

E. Music Festival

The boy Vitya loves to listen to music very much. He knows that $n$ albums are due to be released this Friday, $i$-th of which contains $k_i$ tracks. Of course, Vitya has already listened to all the tracks, and knows that in the $i$-th album, the coolness of the $j$-th track is equal to $a_{i,j}$.

Vitya has a friend Masha, whom he really wants to invite to the festival, where his favorite bands perform. However, in order for a friend to agree, she must first evaluate the released novelties. Vitya knows that if Masha listens to a track that was cooler than all the previous ones, she will get 1 unit of impression. Unfortunately, albums can only be listened to in their entirety, without changing the songs in them in places.

Help Vitya find such an order of albums so that Masha's impression turns out to be as much as possible, and she definitely went to the festival with him.

Input

Each test consists of multiple test cases. The first line contains a single integer t ($1 \le t \le 200\,000$) — the number of test cases. The description of test cases follows.

The first line of each test case contains a single integer $n$ ($1 \le n \le 200\,000$) — number of albums.

The album descriptions follow. Each album description consists of two lines:

The first line contains a single integer $k_i$ ($1 \le k_i \le 200\,000$) — the number of tracks in the $i$th album.

The following line contains $k_i$ integers $a_{i, 1},\ a_{i, 2},\ a_{i, 3},\ \ldots,\ a_{i, k_i}$ ($1 \le a_{i,j} \le 200\,000$) — the coolness of the tracks in the $i$ album.

Denote for $\sum k_i$ the sum of all $k_i$. It is guaranteed that $\sum k_i \le 200\,000$.

Output

For each test case print the singular number — the maximum impression that Masha can get.

Example

input

2
4
5
4 9 4 6 8
1
7
2
8 6
1
1
4
2
3 4
2
1 8
2
2 8
2
7 9

Note

4
4

In the first test example, the optimal order is listening to the 4th, 2nd, 3rd and 1st albums.

In this case, Masha will listen to the tracks in the following order: 1; 7; 8, 6; 4, 9, 4, 6, 8 and will receive 4 units of impression.

In the second test example, you must first listen to the 1st, then the 4th, and in any order the 2nd and 3rd. In this case, Masha will get the maximum impression, and for every song in the 1st and 4th albums and nothing for the 2nd and 3rd.

 

解题思路

  官方给出的题解有点看不懂,这里记录我想到的做法,主要是dp的状态定义不一样。

  首先要想到对于任意一个序列$a_i$,其中有些元素是没用的。这是由于一个序列的贡献是从第一个元素开始严格递增的元素个数,而其他的数没用贡献,可以删除。比如$[\textbf{1}, \textbf{4}, 4, 3, \textbf{6}, 5, 6]$,那么实际上这个序列完全等价于$[1, 4, 6]$。

  我们对每一个序列都去掉无用元素,然后按照每个序列最后一个元素(即最大的元素)从小到大排序,依次选择。我们先定义dp的状态再解释为什么要从小到大排序。

  定义状态$f(i)$表示从前$i$个序列中选,且将第$i$个序列接在最后的所有方案的集合,属性就是拼接序列贡献的最大。根据第$i$个序列接在前面哪个序列来进行状态划分,因此在他转移方程就是$$f(i) = \max\limits_{0 \leq j < k'_i} \left\{ \max\limits_{1 \leq v \leq u} \{ f(v) \} + k'_i - j \right\}$$

  注意序列编号是$1 \sim n$,序列中元素的编号是$0 \sim k'_i - 1$。其中$k'_i$表示删除无用元素后序列$a_i$的大小,$u$是满足$a_{u,k'_u} < a_{i,j}$的最大的下标$u$。

  根据这个状态$f(i)$可以发现贡献的统计都是只考虑前$i$个序列所构成的方案,对于没选到或者$i$之后的序列都是默认不产生贡献的。因此如果序列$a_j$的最大值小于$a_i$的最大值,而$a_j$排在$a_i$的后面,那么对于状态$f(j)$,序列$a_i$一定没有贡献。对于状态$f(i)$,序列$a_j$一定没有贡献。即不会有$a_j$和$a_i$同时有贡献的方案,这就会少考虑一种产生贡献的拼接方案。而如果保证最大值从小到大排列,那么就会发现这三种方案都会考虑到(还应该包括既没有$a_j$和$a_i$贡献的情况)。

  或者另外一种理解的话,可以发现对于一个拼接序列,从左往右看产生贡献的序列,这些序列一定会是最大值从小到大排列的。因此我们在dp时只考虑会产生贡献的序列,即按照序列的最大值从小到大来选择,这与原来的方案是等价的。

  如果不考虑优化的话,上面做法的时间复杂度是$O(k^2 \cdot \log{k})$。其中$u$可以二分出来,而$\max\limits_{1 \leq v \leq u} \{ f(v) \}$本质就是求状态$f$的一个前缀最大值,可以开个数组来维护,这样时间复杂度就降到了$O(k \cdot \log{k})$了。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 2e5 + 10;
 5 
 6 vector<int> a[N];
 7 int f[N], maxf[N];
 8 
 9 void solve() {
10     int n;
11     scanf("%d", &n);
12     for (int i = 1; i <= n; i++) {
13         a[i].clear();
14         int cnt, last = -1;
15         scanf("%d", &cnt);
16         while (cnt--) {
17             int x;
18             scanf("%d", &x);
19             if (a[i].empty() || x > last) {    // 删去无用元素,保留递增的元素
20                 a[i].push_back(x);
21                 last = x;
22             }
23         }
24     }
25     sort(a + 1, a + n + 1, [&](vector<int> &p, vector<int> &q) {    // 按照每个序列的最大值排序
26         return p.back() < q.back();
27     });
28     memset(f, 0, n + 10 << 2);
29     memset(maxf, 0, n + 10 << 2);
30     for (int i = 1; i <= n; i++) {
31         for (int j = 0; j < a[i].size(); j++) {
32             int l = 0, r = i - 1;
33             while (l < r) {    // 在i前面二分出来最大值不超过a[i][j]的最大序列编号
34                 int mid = l + r + 1 >> 1;
35                 if (a[mid].back() < a[i][j]) l = mid;
36                 else r = mid - 1;
37             }
38             f[i] = max(f[i], maxf[l] + (int)a[i].size() - j);
39         }
40         maxf[i] = max(maxf[i - 1], f[i]);    // 维护f的前缀最大值
41     }
42     printf("%d\n", maxf[n]);
43 }
44 
45 int main() {
46     int t;
47     scanf("%d", &t);
48     while (t--) {
49         solve();
50     }
51     
52     return 0;
53 }

  上面其实是写题解时想到的做法。一开始是用树状数组来优化的,直接开个权值树状数组,在序列最大值处来维护$f$的最大值,因为有多组测试数据,还需要离散化,挺麻烦的。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 2e5 + 10;
 5 
 6 vector<int> a[N];
 7 int f[N];
 8 int tr[N];
 9 int xs[N], sz;
10 
11 int lowbit(int x) {
12     return x & -x;
13 }
14 
15 void add(int x, int c) {
16     for (int i = x; i <= sz; i += lowbit(i)) {
17         tr[i] = max(tr[i], c);
18     }
19 }
20 
21 int query(int x) {
22     int ret = 0;
23     for (int i = x; i; i -= lowbit(i)) {
24         ret = max(ret, tr[i]);
25     }
26     return ret;
27 }
28 
29 int find(int x) {
30     int l = 1, r = sz;
31     while (l < r) {
32         int mid = l + r >> 1;
33         if (xs[mid] >= x) r = mid;
34         else l = mid + 1;
35     }
36     return l;
37 }
38 
39 void solve() {
40     int n;
41     scanf("%d", &n);
42     sz = 0;
43     for (int i = 1; i <= n; i++) {
44         a[i].clear();
45         int cnt, last = -1;
46         scanf("%d", &cnt);
47         while (cnt--) {
48             int x;
49             scanf("%d", &x);
50             if (a[i].empty() || x > last) {
51                 a[i].push_back(x);
52                 xs[++sz] = x;
53                 last = x;
54             }
55         }
56     }
57     sort(a + 1, a + n + 1, [&](vector<int> &p, vector<int> &q) {
58         return p.back() < q.back();
59     });
60     sort(xs + 1, xs + sz + 1);
61     sz = unique(xs + 1, xs + sz + 1) - xs - 1;
62     memset(f, 0, n + 10 << 2);
63     memset(tr, 0, sz + 10 << 2);
64     for (int i = 1; i <= n; i++) {
65         for (int j = 0; j < a[i].size(); j++) {
66             f[i] = max(f[i], query(find(a[i][j]) - 1) + (int)a[i].size() - j);
67         }
68         add(find(a[i].back()), f[i]);
69     }
70     printf("%d\n", *max_element(f, f + n + 1));
71 }
72 
73 int main() {
74     int t;
75     scanf("%d", &t);
76     while (t--) {
77         solve();
78     }
79     
80     return 0;
81 }

 

参考资料

  Codeforces Round #857 Editorial:https://codeforces.com/blog/entry/113857

posted @ 2023-03-15 12:17  onlyblues  阅读(28)  评论(0编辑  收藏  举报
Web Analytics