前(后)缀和的神奇应用

D-又放学辣(进阶)_牛客小白月赛80

最大值的最小可能,可以用二分答案解决,但是题目有m次输出,直接二分答案时间复杂度为m^2logn,肯定会超时。

所以我们可以先预处理出来如果还留在学校的人数最多的班级的最少的可能人数为x时,需要走的人数是多少。

第一种方法:我们可以用差分的方法求出数组BB_i表示人数大于等于i的班级数量。再对B数组求一次前缀和,B_i表示还留在学校的人数最多的班级的最少数量为i时,学校中剩下的总人数为B_i

实现代码:

for (int i = 1; i <= n; i++) {
    int x; cin >> x;
    A[x]++;
}

for(int i = 1; i <= m; i ++)
{
    if(A[i]) B[1] ++, B[A[i] + 1] --; //求差分数组
}

//求人数大于等于i的班级数量
for(int i = 1; i <= n; i ++)B[i] += B[i - 1];

//还留在学校的人数最多的班级的最少数量为时,学校中剩下的总人数
for(int i = 1; i <= n; i ++)B[i] += B[i - 1];

然后我们去二分可能的答案,对于每一个mid,应该走的学生数量(res)为n-B[mid]。如果A[i]>mid,说明我们多算了一部分拖堂的人数,这种情况需要将res-(A[i]-mid)。得到的res如果大于k,说明mid的值太小了,l右移;否则说明mid的值太大了,r左移。

AC代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 1e6 + 7;

int A[N], B[N];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    int sum = n;
    int mx = 0;
    for (int i = 1; i <= n; i++) {
        int x; cin >> x;
        A[x]++;
    }
    for(int i = 1; i <= m; i ++)
    {
        if(A[i]) B[1] ++, B[A[i] + 1] --;
    }
    for(int i = 1; i <= n; i ++)B[i] += B[i - 1];
    for(int i = 1; i <= n; i ++)B[i] += B[i - 1];
    for (int i = 1; i <= m; i++){
        if (sum - A[i] < k) {
            cout << "-1 ";
            continue;
        }
        int l = 0, r = n;
        while (l < r){
            int mid = l + r >> 1;
            ll res = n - B[mid];
            if (A[i] > mid) res -= (A[i] - mid);
            if (res <= k) r = mid;
            else l = mid + 1;
        }
        cout << l << ' ';
    }
    return 0;
}

第二种解法:我们可以用后缀和求出如果还留在学校的人数最多的班级的最少的可能人数为i时,需要走的人数是多少。(第一次了解到这种解法,还不太理解)

实现代码:

for (int i = 1; i <= n; i++) {
    int x; cin >> x;
    A[x]++;
}

for (int i = 1; i <= m; i++){
    if (A[i]) B[A[i] - 1]++; 
}
for (int i = n; i >= 0; i--) B[i] += B[i + 1];

//直接求出如果还留在学校的人数最多的班级的最少的可能人数为i时,需要走的人数是多少
for (int i = n; i >= 0; i--) B[i] += B[i + 1];

然后我们去二分可能的答案,对于每一个mid,应该走的学生数量(res)为B[mid]。如果A[i]>mid,说明我们多算了一部分拖堂的人数,这种情况需要将res-(A[i]-mid)。得到的res如果大于k,说明mid的值太小了,l右移;否则说明mid的值太大了,r左移。

AC代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 1e6 + 7;

ll A[N], B[N];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    int sum = n;
    int mx = 0;
    for (int i = 1; i <= n; i++) {
        int x; cin >> x;
        A[x]++;
    }
    for (int i = 1; i <= m; i++){
        if (A[i]) B[A[i] - 1]++;
    }
    for (int i = n; i >= 0; i--) B[i] += B[i + 1];
    for (int i = n; i >= 0; i--) B[i] += B[i + 1];
    for (int i = 1; i <= m; i++){
        if (sum - A[i] < k) {
            cout << "-1 ";
            continue;
        }
        int l = 0, r = n;
        while (l < r){
            int mid = l + r >> 1;
            ll res = B[mid];
            if (A[i] > mid) res -= (A[i] - mid);
            if (res <= k) r = mid;
            else l = mid + 1;
        }
        cout << l << ' ';
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值