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 }
View Code

 

posted @ 2020-05-20 16:23  古比  阅读(187)  评论(0编辑  收藏  举报