题解:P13530 [OOI 2023] Music Festival / 音乐节

原题链接

形式化题意

给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 ki200000,同时发现如果一些情况下每个序列只保留后缀与保留整个序列是等效的,例如: [ 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=jmaxi1,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;
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值