ccpc2023桂林(铜牌银牌)题解

I. Barkley II

题目大意:
给定一个有 n n n 个数的序列 a a a ,数字的大小不超过 m m m ,对于区间 [ l , r ] [l, r] [l,r] 它的价值为区间当中数的种类数减去 M E X MEX MEX 值。 请你输出最大价值。

( 1 ≤ n ≤ 5 × 1 0 5 ) (1 \le n \le 5 \times 10^5) (1n5×105) ( 1 ≤ m ≤ 5 × 1 0 5 ) (1 \le m \le 5 \times 10^5) (1m5×105) ,保证总的 n n n 的大小不超过 5 × 1 0 5 5 \times 10^5 5×105 m m m 的大小不保证。

解题思路:
我们考虑枚举 M E X MEX MEX 的值(当然并不是直接枚举),以 M E X MEX MEX 为分界线把区间分割成一个个的小区间,我们只需要计算每一段小区间的价值取 m a x max max 即为答案。

那么我们就有一个疑问:这样分割出的区间的实际 M E X MEX MEX 可能会小于我们当前枚举的 M E X MEX MEX ,这样对吗?
当然是正确的,因为我们是在枚举的过程中取 m a x max max ,上述情况一定不是最优解的情况,因此对答案不会有影响。(模拟几个样例就明白了)

怎么计算出每个小区间的价值?
问题转化成:给定一个区间,求这个区间中数字的种类数。答案是用树状数组来维护即可。
洛谷例题:HH的项链
做会这道题目就学会了。

M E X MEX MEX 怎么枚举?
提前把每个数的位置存储到二维数组 p o s pos pos 中,再枚举 M E X = a [ i ] MEX = a[i] MEX=a[i] ,假设当前这个 a [ i ] a[i] a[i] 的位置是 r = p o s [ a [ i ] ] [ j ] r = pos[a[i]][j] r=pos[a[i]][j] ,那么上一个 a [ i ] a[i] a[i] 的位置就是 l = p o s [ a [ i ] ] [ j − 1 ] l = pos[a[i]][j - 1] l=pos[a[i]][j1] ,因此这段小区间的价值就是 f e n . s u m ( r − 1 ) − f e n . s u m ( l ) − M E X fen.sum(r - 1) - fen.sum(l) - MEX fen.sum(r1)fen.sum(l)MEX

代码:

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;

const int N = 5e5 + 10;

template<class T>
struct Fenwick {
    int n;
    vector<T> a;

    
    Fenwick(int n = 0) {
        init(n);
    }

    void init(int n) {
        this->n = n;
        a.assign(n, T());
    }

    void add(int p, T x) {
        for (int i = p; i < n; i += i & -i) {
            a[i] += x;
        }
    }

    T sum(int p) {
        T res = 0;
        for (int i = p; i > 0; i -= i & -i) {
            res += a[i];
        }
        return res;
    }
};

vector<vector<int>> pos(N);
vector<int> pt(N), p(N), cnt(N);

void solve() {
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        pos[a[i]].emplace_back(i);
        cnt[a[i]] = 1;
    }

    int ans = -1e9;
    Fenwick<int> fen(n + 1);
    for (int i = 1; i <= n; i++) {
        int mex = a[i];
        int r = i - 1, l;
        if (pos[mex][pt[mex]] == i) {
            l = 0;
        } else {
            l = pos[mex][pt[mex]];
            pt[mex]++;
        }
        ans = max(ans, fen.sum(r) - fen.sum(l) - mex);
        if (p[mex]) {
            fen.add(p[mex], -1);
        }
        fen.add(i, 1);
        p[mex] = i;
    }
    for (int i = 1; i <= n; i++) {
        int mex = a[i];
        int r = n, l = *pos[mex].rbegin();
        ans = max(ans, fen.sum(r) - fen.sum(l) - mex);
    }
    for (int i = 1; i <= n; i++) {
        if (!cnt[i]) {
            ans = max(ans, fen.sum(n) - i);
            break;
        }
    }
    cout << ans << '\n';

    for (int i = 1; i <= n; i++) {
        pos[a[i]].clear();
        pt[a[i]] = 0;
        p[a[i]] = 0;
        cnt[a[i]] = 0;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--) {
        solve();
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值