2020 CCPC 秦皇岛 E -Exam Results(尺取/树状数组)

传送门


题目大意

给出 n n n个学生,每个学生都有两个分数,可以任选一个作为最终的分数。然后给定一个阈值 p p p,设最终所有学生的最高分为 x x x,那么所有学生的分数 y y y满足 y ≥ x ∗ p y \geq x*p yxp则通过考试,问最多能有多少名学生通过考试。

解题思路

首先这题不能贪心写,具体原因分析之后不难得知。我们考虑将所有学生的两种成绩合并之后然后从小到大排序,那么我们不难发现一个重要信息——最终最高分的取值范围是所有人最低分的最大值到最高分的最大值。然后发现问题转化成了对于给定的最高分 x x x和通过线 p ∗ x p*x px中间有多少学生?

实际上还有不少解法,但是感觉这种是最容易想到的

反思

为什么比赛时想到了同样的思路却认为区间种类数无法维护?还是自己在不断写数据结构题目的过程中,缺少了对每个数据结构常见用途的总结和回顾,后面一定要慢慢调整训练方向!

解法一:尺取

首先可以将每个分数打上学生的编号。考虑最高分单调不减的性质,那么及格线也是单调不减的,我们考虑将问题强制离线,这样得到一系列区间,可能想到使用时间复杂度 O ( n n ) O(n \sqrt{n}) O(nn )的莫队,但是别忘了上面,每个区间的左右边界都是只会右移或者不移动。这样直接对学生的编号尺取不就行了!而且也没必要离线了,只需要从最低分的最大值开始遍历。

下面代码是先取了第一个区间计算答案,然后开始尺取。

//
// Created by Happig on 2020/11/1
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>

using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 4e5 + 10;

struct node {
    int val, id;

    bool operator<(const node &p) const {
        return val < p.val;
    }
} a[maxn];

int T, kase, n, P;

int g[maxn], idx[maxn], num[maxn];

int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--) {
        cin >> n >> P;
        int Max = 0;
        for (int i = 1; i <= n; i++) {
            cin >> a[i].val >> a[n + i].val;
            Max = max(Max, min(a[i].val, a[n + i].val));
            a[i].id = a[n + i].id = i;
        }
        n *= 2;
        sort(a + 1, a + 1 + n);
        for (int i = 1; i <= n; i++) {
            g[i] = a[i].val, idx[i] = a[i].id;
        }
        int start = lower_bound(g + 1, g + 1 + n, Max) - g, res = ceil(1.0 * P * Max / 100.0);
        int l = lower_bound(g + 1, g + 1 + n, res) - g;
        int cur = 0;
        memset(num, 0, sizeof num);
        for (int i = l; i <= start; i++) {
            if (!num[idx[i]]) ++cur;
            ++num[idx[i]];
        }
        int ans = cur;
        for (int r = start + 1; r <= n; r++) {
            if (!num[idx[r]]) ++cur;
            ++num[idx[r]];
            while (1LL * P * g[r] > 100LL * g[l]) {
                if (--num[idx[l]] == 0) cur--;
                l++;
            }
            ans = max(ans, cur);
        }
        cout << "Case #" << ++kase << ": " << ans << ENDL;
    }
    return 0;
}
解法二:树状数组

直到看到别人的题解我才意识到,树状数组离线维护区间种类数的问题我在暑假写过,即HH的项链

根据解法一研究的思路,离线保存查询区间后,将询问按照右端点升序处理。树状数组能解决依据一个重要的性质:当 r r r固定后,左边出现的相同的数会产生相同的贡献,为了消除前面的同类型的数产生贡献,从左向右更新记录之前出现的位置然后从树状数组该位置起减去这个数的贡献。这样对于相同右端点的所有查询,得到的答案都是正确的!具体分析过程见HH的项链题解

//
// Created by Happig on 2020/11/1
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>

using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 4e5 + 10;

struct node {
    int val, id;

    bool operator<(const node &p) const {
        return val < p.val;
    }
} a[maxn];

struct line {
    int l, r;

    bool operator<(const line &p) const {
        return r < p.r;
    }
} q[maxn];

int T, kase, n, P;

int g[maxn], idx[maxn], t[maxn], pre[maxn];

void update(int i, int k) {
    while (i <= n) {
        t[i] += k;
        i += lowbit(i);
    }
}

int getSum(int i) {
    int ans = 0;
    while (i) {
        ans += t[i];
        i -= lowbit(i);
    }
    return ans;
}


int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--) {
        cin >> n >> P;
        int Max = 0;
        for (int i = 1; i <= n; i++) {
            cin >> a[i].val >> a[n + i].val;
            Max = max(Max, min(a[i].val, a[n + i].val));
            a[i].id = a[n + i].id = i;
        }
        n *= 2;
        sort(a + 1, a + 1 + n);
        for (int i = 1; i <= n; i++) {
            g[i] = a[i].val, idx[i] = a[i].id;
        }
        int cnt = 0;
        int start = lower_bound(g + 1, g + 1 + n, Max) - g;
        for (int i = start; i <= n; i++) {
            int res = ceil(1.0 * P * g[i] / 100.0);
            int pos = lower_bound(g + 1, g + 1 + n, res) - g;
            //cout << pos << endl;
            q[++cnt] = {pos, i};
        }
        sort(q + 1, q + 1 + cnt);
        int s = 1, ans = 0;
        memset(t, 0, sizeof t);
        memset(pre, 0, sizeof pre);
        for (int i = 1; i <= cnt; i++) {
            while (s <= q[i].r) {
                if (pre[idx[s]]) update(pre[idx[s]], -1);
                update(s, 1);
                pre[idx[s]] = s;
                s++;
            }
            ans = max(ans, getSum(q[i].r) - getSum(q[i].l - 1));
        }
        cout << "Case #" << ++kase << ": " << ans << ENDL;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值