Problem Description
链接:https://ac.nowcoder.com/acm/contest/4743/E
来源:牛客网
有n堆石子,第i堆石子的石子数量是ai,作为牛客网的一头领头牛,牛牛决定把这些石子搬回牛客。如果牛牛一次搬运的石子数量是k,那么这堆石子将对牛牛产生k2的负担值。牛牛最多只能搬运m次,每次搬运可以从一堆石子中选出一些石子搬回牛客,每次搬运不能同时从两堆石子中选取石子,每次只能搬运整数个石子。牛牛是一只聪明的牛,他想出了一种搬运计划可以最小化他搬运完这些石子的负担值的总和,但是突然牛牛的死敌牛能出现了,牛能每次可以施展以下的魔法 x v将第x堆石子的数量变为v 这打乱了牛牛的计划,每次牛能施展一次魔法,牛牛就得重新规划他的搬运方案,但是牛能施展魔法的次数太多了,牛牛根本忙活不过来了,于是他请来了聪明的你帮他写一个程序计算。
Input
第一行两个整数n,m
第二行n个整数a1,a2…an
第三行一个整数 q,表示牛能施展魔法的次数
接下来q行每行两个整数 x v,表示牛能将第x堆石子的数量变为了v
Output
输出包含q行,每行输出一个整数表示在牛能施展魔法后,牛牛搬完所有n堆石子所产生最小化的负担值的总和。
Examples
Input
3 4
2 2 2
3
1 2
1 3
2 6
Output
10
13
31
【题目链接】石子搬运
【思路】
这道题还是有点意思的,做出来走了一些弯路
首先可以知道,我们把一堆石子均分可以使答案尽量小,因为n堆m次搬运,所以可以分解石子堆(m-n)次,所以首先考虑用一个优先队列把每堆石子的数量放进去,然后每次弹出一个最大值,再把这个值均分后得到的两个数再放回到优先队列里,最后用队列里的值去计算平方和。
乍一看很对,但是忽略了一种情况,那就是均分两次得到三份的解可能没有一次均分成三份的解优,比如,12用前面的方法分成{6,3,3},而用后面的方法则能变为{4,4,4},显然后面的平方和更小。
于是,我就考虑先把原来的平方和算出来,然后把每一堆均分为1份,2份…(m-n)份,再把每多分一次对答案的减少至放在优先队列里,最后队列中取前(m-n)个元素即可
这样答案毫无疑问是对的,但是每修改一次都要计算所有石子堆多分一次的结果,复杂度就在 ,提交会超时,于是考虑先计算一遍所有石子堆的情况,放在优先队列里,然后如果某个堆的石子数要发生变化,就先把之前这堆对队列里的贡献值删去,再加入新的贡献值。
但是优先队列无法O(n)删除(这里n指队列元素n*n),于是就用数组模拟一下删除的过程,而且我们不需要维护所有的元素,因为我们只需要m-n个,但是把维护的范围固定在前(m-n)大又不行,因为可能我把原来一个很大的石子堆改成了很小的石子堆,那么可能之前那个石子堆的贡献值有些不在前(m-n)内,但是比现在这个石子堆的贡献要大,所以维护区间需要大一点(大m-n足够,因为前一个对答案的贡献个数最多是m-n)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
const int maxn=405;
const int INF=0x3f3f3f3f;
const ll mod=998244353;
int n,m;
ll a[maxn];
ll add[maxn];
map<ll,int>mp;
int main()
{
scanf("%d%d",&n,&m);
ll sum=0;
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum+=a[i]*a[i];
int T;
scanf("%d",&T);
priority_queue<ll,vector<ll>,less<ll> >q;
for(int i=1;i<=n;i++)
{
int kk=m-n;
ll pre=a[i]*a[i];
for(int j=1;j<=kk;j++)
{
ll fen=a[i]/(j+1);
ll res=a[i]%(j+1);
ll now=fen*fen*(j+1-res)+(fen+1)*(fen+1)*res;
q.push(pre-now);
pre=now;
}
}
int sz=0;
int cc=(m-n)*2;
while(q.size())
{
if(sz<cc) add[sz++]=q.top();
q.pop();
}
while(T--)
{
int id;
ll v;
scanf("%d%lld",&id,&v);
sum-=a[id]*a[id];
mp.clear();
ll pre=a[id]*a[id];
for(int i=1;i<=m-n;i++) //标记这堆之前对答案的贡献
{
ll fen=a[id]/(i+1);
ll res=a[id]%(i+1);
ll now=fen*fen*(i+1-res)+(fen+1)*(fen+1)*res;
mp[pre-now]++;
pre=now;
}
for(int i=0;i<sz;i++) //删除之前的贡献
{
if(mp[add[i]]==0) q.push(add[i]);
else mp[add[i]]--;
}
a[id]=v;
sum+=v*v;
pre=a[id]*a[id];
for(int i=1;i<=m-n;i++) //加上现在的贡献
{
ll fen=a[id]/(i+1);
ll res=a[id]%(i+1);
ll now=fen*fen*(i+1-res)+(fen+1)*(fen+1)*res;
q.push(pre-now);
pre=now;
}
ll ans=sum;
sz=0;
for(int i=1;i<=m-n;i++)
{
ll x=q.top();
//printf("$$%lld\n",x);
add[sz++]=x;
ans-=x;
q.pop();
}
while(q.size())
{
ll x=q.top();
if(sz<(m-n)*2) add[sz++]=x;
q.pop();
}
printf("%lld\n",ans);
}
}