E 石子搬运 优先队列+思维
链接:https://ac.nowcoder.com/acm/contest/4743/E
思路:
首先可以知道,我们把一堆石子均分可以使答案尽量小,因为n堆m次搬运,所以可以分解石子堆(m-n)次,所以首先考虑用一个优先队列把每堆石子的数量放进去,然后每次弹出一个最大值,再把这个值均分后得到的两个数再放回到优先队列里,最后用队列里的值去计算平方和。
乍一看很对,但是忽略了一种情况,那就是均分两次得到三份的解可能没有一次均分成三份的解优,比如,12用前面的方法分成{6,3,3},而用后面的方法则能变为{4,4,4},显然后面的平方和更小。
于是,我就考虑先把原来的平方和算出来,然后把每一堆均分为1份,2份…(m-n)份,再把每多分一次对答案的减少至放在优先队列里,最后队列中取前(m-n)个元素即可
这样答案毫无疑问是对的,但是每修改一次都要计算所有石子堆多分一次的结果,复杂度就在O(q∗n2) O(q*n^2)O(q∗n2),提交会超时,于是考虑先计算一遍所有石子堆的情况,放在优先队列里,然后如果某个堆的石子数要发生变化,就先把之前这堆对队列里的贡献值删去,再加入新的贡献值。
但是优先队列无法O(n)删除(这里n指队列元素n*n),于是就用数组模拟一下删除的过程,而且我们不需要维护所有的元素,因为我们只需要m-n个,但是把维护的范围固定在前(m-n)大又不行,因为可能我把原来一个很大的石子堆改成了很小的石子堆,那么可能之前那个石子堆的贡献值有些不在前(m-n)内,但是比现在这个石子堆的贡献要大,所以维护区间需要大一点(大m-n足够,因为前一个对答案的贡献个数最多是m-n)
————————————————
版权声明:本文为CSDN博主「Dust_Heart」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/my_sunshine26/java/article/details/104850194
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 #define mst(a,b) memset((a),(b),sizeof(a)) 5 #define rush() int T;scanf("%d",&T);while(T--) 6 const int maxn=405; 7 const int INF=0x3f3f3f3f; 8 const ll mod=998244353; 9 10 int n,m; 11 ll a[maxn]; 12 ll add[maxn]; 13 map<ll,int>mp; 14 15 int main() 16 { 17 scanf("%d%d",&n,&m); 18 ll sum=0; 19 for(int i=1;i<=n;i++){ 20 scanf("%lld",&a[i]); 21 sum+=a[i]*a[i]; 22 } 23 int T; 24 scanf("%d",&T); 25 priority_queue<ll,vector<ll>,less<ll> >q; 26 for(int i=1;i<=n;i++){ 27 int kk=m-n; 28 ll pre=a[i]*a[i]; 29 for(int j=1;j<=kk;j++){ 30 ll fen=a[i]/(j+1); 31 ll res=a[i]%(j+1); 32 ll now=fen*fen*(j+1-res)+(fen+1)*(fen+1)*res; 33 q.push(pre-now); 34 pre=now; 35 } 36 } 37 int sz=0; 38 int cc=(m-n)*2; 39 while(q.size()){ 40 if(sz<cc) add[sz++]=q.top(); 41 q.pop(); 42 } 43 while(T--){ 44 int id; 45 ll v; 46 scanf("%d%lld",&id,&v); 47 sum-=a[id]*a[id]; 48 mp.clear(); 49 ll pre=a[id]*a[id]; 50 for(int i=1;i<=m-n;i++) //标记这堆之前对答案的贡献 51 { 52 ll fen=a[id]/(i+1); 53 ll res=a[id]%(i+1); 54 ll now=fen*fen*(i+1-res)+(fen+1)*(fen+1)*res; 55 mp[pre-now]++; 56 pre=now; 57 } 58 59 for(int i=0;i<sz;i++) //删除之前的贡献 60 { 61 if(mp[add[i]]==0) q.push(add[i]); 62 else mp[add[i]]--; 63 } 64 65 a[id]=v; 66 sum+=v*v; 67 68 pre=a[id]*a[id]; 69 for(int i=1;i<=m-n;i++) //加上现在的贡献 70 { 71 ll fen=a[id]/(i+1); 72 ll res=a[id]%(i+1); 73 ll now=fen*fen*(i+1-res)+(fen+1)*(fen+1)*res; 74 q.push(pre-now); 75 pre=now; 76 } 77 78 ll ans=sum; 79 sz=0; 80 for(int i=1;i<=m-n;i++){ 81 ll x=q.top(); 82 add[sz++]=x; 83 ans-=x; 84 q.pop(); 85 } 86 while(q.size()){ 87 ll x=q.top(); 88 if(sz<(m-n)*2) add[sz++]=x; 89 q.pop(); 90 } 91 printf("%lld\n",ans); 92 } 93 return 0; 94 }