题目
有一个长度为 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;
}