Scapegoat Gym - 101775B (贪心+推公式)
题目链接https://vjudge.csgrandeur.cn/problem/Gym-101775B
原文
题意:
现在某人闯祸了,产生了 N 个锅,每个锅有个严重点数,现在可以安排 M 个替罪羊去背锅。
每个替罪羊最多只能背一个锅。若一只羊背一个锅,则该锅的严重点数全部算在它头上;若多只羊背同一个锅,则每个羊分到该锅的一部分的严重点数。
现在考虑一种安排方案,使得所有的身上的严重点数的方差最小。
题解
先考虑上 N 只羊一一对应地背 N 个锅,剩下 M−N 个替罪羊身上严重点数均为 0,当然这样并不是最优解。
应当把再剩下 M−N 个替罪羊安排进 N 个锅里分摊责任,使得方差减小。考虑贪心的思路,每次安排进去一只羊,都要使得方差减小最多。
考虑将新来的羊安排到某个任务,该任务严重点数为 a[i],且原来的背锅羊数是 num,那么首先每个替罪羊分到的严重点数的平均数肯定是不变的
\(\overline{X} = \frac{a[1]+ a[2]+ \cdots + a[n]}{m}\),因此它原本对方差的贡献为\(\frac{1}{m} \cdot num \cdot (\frac{a[i]}{num}-\overline{X})^2\)。
而现在新加进去一个替罪羊,这个任务对方差的新的贡献为 \(\frac{1}{m} \cdot (num+1) \cdot (\frac{a[i]}{num+1}-\overline{X})^2\)
显然,方差的差值就是\(\Delta = num \cdot (\frac{a[i]}{num}-\overline{X})^2 -(num+1) \cdot (\frac{a[i]}{num+1}-\overline{X})^2 = \frac{a[i]^2}{num \cdot (num+1)} - \overline{X}^2\)
因为平均数不变,因此我们让每次挑一个任务让它的$ \frac{a[i]^2}{num \cdot (num+1)}$最大就好了,这个可以用优先队列。
因此代码整体思路就是:
- 存储替罪羊的数量:num。
- 将节点放入优先队列,按照$ \frac{a[i]^2}{num \cdot (num+1)}$排序
- 利用num求出每个锅对方差的贡献,相加
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
const int N=5e5+10;
// const double eps=1e-10;
struct node{
int id, num,a;
double get() const{
return a*a*1.0/num/(num+1);
}
bool operator<(const node &t) const {
return t.get()>this->get();
}
};
signed main()
{
int t;cin>>t;
int Case=1;
while(t--){
priority_queue<node> q;
int n,m;
scanf("%lld %lld",&n,&m);
double aver=0;
for(int i=1;i<=n;i++){
int x;
scanf("%lld",&x);
aver+=x;
q.push({i,1,x});
}
aver/=m;
for(int i=0;i<m-n;i++){
node x=q.top();q.pop();
x.num+=1;
q.push(x);
}
double ans=0;
while(q.size()){
node x=q.top();q.pop();
ans+=x.num*(x.a*1.0/x.num-aver)*(x.a*1.0/x.num-aver);
}
ans/=m;
printf("Case #%lld: %.12lf\n",Case++,ans);
}
return 0;
}