CF 1801 C
题目描述
有 \(N\) 个专辑,第 \(i\) 个专辑中有 \(k_i\) 首歌曲,其中第 \(j\) 首歌的酷炫程度为 \(a_{i,j}\)。
你会选择一个排列 \(p_1,p_2,\dots,p_N\),每次你会将 \(p_i\) 中所有歌曲从前往后依次听完。每当你遇到一个严格大于之前听过所有歌曲的歌曲,则你会对这个歌曲产生印象。
求最多能有多少个令你产生印象的歌曲。
思路
考虑贪心地求解。
很容易想到这样一种贪心:按每个专辑的最大值从小到大排序,然后做 dp。
但是不是对的呢?假设我们其中有一个专辑 \(i\) 不是从小到大排序的,即 \(\max\limits_{j=1}^{k_i} \{a_{i,j}\}>\max\limits_{j=1}^{k_{i+1}} \{a_{i+1,j}\}\),那么此时 \(i,i+1\) 都无法互相转移。但将 \(i,i+1\) 交换,它才有可能转移。而多一些转移一定不会使答案更劣,所以此贪心正确。
dp 使用树状数组优化即可。
这里清空要注意不能每次把整个树状数组清空,而要记录被修改过的位置进行清空。
空间复杂度 \(O(N+\max \limits_{1\le i\le N,1\le j \le k_i}\{a_{i,j}\} + \sum \limits_{i=1}^N k_i \log \max \limits_{1\le i\le N,1\le j \le k_i}\{a_{i,j}\})\),时间复杂度 \(O(N\log N + \sum \limits_{i=1}^N k_i \log \max \limits_{1\le i\le N,1\le j \le k_i}\{a_{i,j}\})\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200001, MAXV = 200002;
int t, n, id[MAXN], Max[MAXN], tr[MAXV];
vector<int> a[MAXN], pos;
int lowbit(int x) {
return x & -x;
}
void update(int p, int x) {
p++;
for(; p < MAXV; tr[p] = max(x, tr[p]), pos.push_back(p), p += lowbit(p)) {
}
}
int Getmax(int p) {
p++;
int Max = 0;
for(; p; Max = max(Max, tr[p]), p -= lowbit(p)) {
}
return Max;
}
void Solve() {
cin >> n;
pos.clear();
for(int i = 1, k; i <= n; ++i) {
cin >> k;
id[i] = i;
Max[i] = 0;
a[i].clear();
a[i].resize(k + 1);
for(int j = 1; j <= k; ++j) {
cin >> a[i][j];
Max[i] = max(Max[i], a[i][j]);
}
}
sort(id + 1, id + n + 1, [](const int &a, const int &b) {
return Max[a] < Max[b];
});
for(int i = 1; i <= n; ++i) {
int maxx = 0, res = 0;
for(int j = 1; j < int(a[id[i]].size()); ++j) {
if(a[id[i]][j] > maxx) {
res = max(res, Getmax(a[id[i]][j] - 1)) + 1;
maxx = a[id[i]][j];
}
}
update(Max[id[i]], res);
}
cout << Getmax(MAXV - 2) << "\n";
pos.erase(unique(pos.begin(), pos.end()), pos.end());
for(int x : pos) {
tr[x] = 0;
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for(cin >> t; t--; Solve()) {
}
return 0;
}