平均(寒假每日一题+贪心)

文章讨论了如何在一个长度为10的倍数的数组中,通过调整每个元素的值,使得10种数字出现次数相等,同时计算出最小的总代价。作者提供了两种算法思路,一种利用vector存储和排序,另一种使用结构体进行前缀和操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

有一个长度为 n 的数组(n是 10的倍数),每个数 a[i] 都是区间 [0,9] 中的整数。小明发现数组里每种数出现的次数不太平均,而更改第 i 个数的代价为 b[i],他想更改若干个数的值使得这 10种数出现的次数相等(都等于 n/10),请问代价和最少为多少。

输入格式

输入的第一行包含一个正整数 n。

接下来 n行,第 i行包含两个整数 a[i],b[i],用一个空格分隔。

输出格式

输出一行包含一个正整数表示答案。

数据范围

对于 20%20% 的评测用例,n≤1000;
对于所有评测用例,n≤10^5,0<b[i]≤2×10^5。

输入样例:

10
1 1
1 2
1 3
2 4
2 5
2 6
3 7
3 8
3 9
4 10

输出样例:

27

样例解释

只更改第 1,2,4,5,7,81,2,4,5,7,8 个数,需要花费代价 1+2+4+5+7+8=271+2+4+5+7+8=27。

算法思路

        从题目要求可以看出,数字只有0~9,要将其数组中的数字个数平均分配,比如有10个数,那么数组中0只能有一个,如果多出来了那么将多出来的0转换为其他缺少的数字,我们可以从图中看出,因为0转换为任意数字的花费是相同的且花费都是>0的,那么我们只要将所有数量大于平均数的数,将其花费排序,并取前(总数量-平均数量)的花费,便可以求出答案。

 

程序代码

        两种方法,基本思路是相同的,其中方法二与方法一的时间复杂度是相同的O(nlogn),但方法二的代码长度太长,主要原因:方法一的存储方式比起方法二要好,方法一使用的是vector的存储,方法二的存储方法是结构体(二分+前缀和),其不便利的地方便是,每次都要求左右边界(即:一个数字第一次出现和最后一次出现)这样便可以确定该数字的数量是否超过平均值,利用前缀和便可知道该数字要花费的最小代价(局部选择),将所有数字花费的最小代价加起来(全局最优)便是其答案。

        方法一:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 100010;

vector<int> w[N];

int main()
{
    int n;
    scanf("%d",&n);
    
    for(int i=0;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        //将a对应的代价加入——y
        w[x].push_back(y);
    }
    
    //从题中可以看出会报int 最大10^10左右
    LL ans=0;
    //计算平均数量
    int avg=n/10;
    
    for(int i=0;i<10;i++){
        //该数字的数量
        int num=w[i].size();
        //判断该数字数量是否大于平均数
        if(num>avg){
            //将该数字的要花费的代价排序
            sort(w[i].begin(),w[i].end());
               
            //计算该数字的最小代价
            for(int j=0;j<num-avg;j++){
                ans+=w[i][j];
            }
        }
    }
    
    printf("%lld",ans);
    return 0;
}

方法二:

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

typedef long long LL;

struct node
{
    LL a,b,sum;
    bool operator <(const node &x)const{
        if(a<x.a) return true;
        else if(a==x.a) return b<x.b;
        else return false;
    }
}nodes[N];

int n,m,nexp;
LL ans;

int breach_left(int left,int right,int x){
    while(left<right){
        int mid=(left+right)/2;
        
        if(nodes[mid].a>=x) right=mid;
        else left=mid+1;
    }
    
    if(nodes[left].a==x) return left;
    else return -1;
}

int breach_right(int left,int right,int x){
    while(left<right){
        int mid=(left+right+1)/2;
        
        if(nodes[mid].a<=x) left=mid;
        else right=mid-1;
    }
    
    if(nodes[left].a==x) return left;
    else return -1;
}

LL findValue(int left,int right){
    return nodes[right].sum-nodes[left-1].sum;
}

int main()
{
    scanf("%d",&n);
    m=n/10;
    
    for(int i=1;i<=n;i++){
        LL x,y;
        scanf("%lld%lld",&x,&y);
        nodes[i]={x,y,0};
    }
    
    sort(nodes+1,nodes+n+1);
    nodes[0]={0,0,0};
    
    //求前缀和
    for(int i=1;i<=n;i++){
        nodes[i].sum=nodes[i-1].sum+nodes[i].b;
    }
    
    
    for(int i=0;i<10;i++){
        //第一次出现i的下标
        int left=breach_left(nexp,n,i);
        //最后一次出现的下表
        int right=breach_right(nexp,n,i);
        
        nexp=left;
        
        
        if(left!=-1){
            int tmp=right-left+1;
            if(tmp>m){
                ans+=findValue(left,left+tmp-m-1);
            }
        }
    }
    
    printf("%lld",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陷入回忆的人

各位帅哥美女,打赏个子呗~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值