【题解】集合操作

题意

定义一个可重集合 s ,一次操作为将 s 中最大值减去 p

小 L 想知道,如果给你 sp 以及操作次数 k ,你能求出最后的集合吗?

k<=10^18

Solution:

因为思路比较有借鉴意义,所以写了。

首先 k 的范围不允许模拟。考虑到最大值有单调性,所以二分最终序列最大值,因为每次减去的数一定,可以直接算。可以证明剩下的操作不超过 n 次,暴力枚举即可。

#include<bits/stdc++.h> using namespace std; typedef long long ll; int n; ll k,p,Max,cnt; ll a[1000005]; vector<ll> ans; priority_queue<ll> q; ll check(ll mid) { cnt=0; for(int i=1;i<=n;i++) { if(a[i]>mid) { ll tmp=(a[i]-mid+p-1)/p; cnt+=tmp; } } return cnt; } int main() { scanf("%d%lld%lld",&n,&k,&p); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]),Max=max(Max,a[i]); } sort(a+1,a+1+n); if(p==0) { for(int i=1;i<=n;i++) printf("%lld ",a[i]); return 0; } ll l=-1e18,r=Max,res=0; while(l<=r) { ll mid=(l+r)>>1; if(check(mid)<=k) { r=mid-1,res=mid; } else { l=mid+1; } } // cout<<res<<endl; check(res); for(int i=1;i<=n;i++) { if(a[i]>res) { ll tmp=(a[i]-res+p-1)/p; a[i]-=tmp*p; } q.push(a[i]); } for(int i=1;i<=k-cnt;i++) { ll x=q.top(); q.pop(); q.push(x-p); } while(q.size()) { ans.push_back(q.top()); q.pop(); } for(int i=n-1;i>=0;i--) printf("%lld ",ans[i]); }

第二种思路是二分临界点。考虑 Max-Min<=p 的情况,此时每次操作最大数时都会放在队列末尾,显然具有单调性。请添加图片描述
于是我们想到二分 i 值,我们发现 i=n 时一定成立(因为 i 第一次被取出说明其他数都减的比它小且不超过 p),所以只需要判断第 i 个数第一次被取出时是否 Max-Min<=p 即可。常数较小。

进一步发现没必要二分,直接减到比 a[n] 小即可。

#include<bits/stdc++.h> using namespace std; typedef long long ll; //代码有锅,应该是细节问题 int n; ll k,p,Max,cnt; ll a[1000005]; vector<ll> ans; priority_queue<ll> q; ll check(int mid) { cnt=0; for(int i=1;i<mid;i++) { if(a[i]>a[mid]) { ll tmp=(a[i]-a[mid]+p-1)/p; cnt+=tmp; } } return cnt; } int main() { scanf("%d%lld%lld",&n,&k,&p); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]),Max=max(Max,a[i]); } sort(a+1,a+1+n),reverse(a+1,a+1+n); if(p==0) { for(int i=1;i<=n;i++) printf("%lld ",a[i]); return 0; } if(n==1) { printf("%lld",a[1]-k*p); return 0; } ll l=1,r=n,res=0; while(l<=r) { ll mid=(l+r)>>1; if(check(mid)<=k) { l=mid+1,res=mid; } else { r=mid-1; } } // cout<<res<<endl; check(res); k-=cnt; if(res==n) { ll tmp=k/n; k%=n; //n 轮一个循环, 取到了 a[n] . for(int i=1;i<=n;i++) { if(a[i]>a[res]) { ll tmp2=(a[i]-a[res]+p-1)/p; a[i]-=tmp2*p; } a[i]-=tmp*p; q.push(a[i]); } for(int i=1;i<=k;i++) { ll x=q.top(); q.pop(); q.push(x-p); } } else { //这里每次把最大数减到比次大数小,可以证明操作次数不超过 n for(int i=1;i<=n;i++) { if(a[i]>a[res]) { ll tmp2=(a[i]-a[res]+p-1)/p; a[i]-=tmp2*p; } q.push(a[i]); } while(k) { ll x=q.top(); q.pop(); ll y=q.top(); ll tmp=(x-y)/p+1; if(tmp<=k) { x-=tmp*p; k-=tmp; q.push(x); } else { x-=k*p; q.push(x); break; } } } while(q.size()) { ans.push_back(q.top()); q.pop(); } for(int i=n-1;i>=0;i--) printf("%lld ",ans[i]); }

讲到单调性,来看这道题:

小L的疑惑

结论:不能支付的面额可以表示为 t-xa-yb(x>=0,y>=0) 其中 t=ab-a-b

由于 k<=10^7 ,通过分析单调性,可以得到线性做法:

维护两个队列,初始将 t 放入 a 队列,每次取出两个队首最大值,如果是 b 栈就放入队尾,a 栈则放入两个栈的队尾,由于每次入栈的规则相同,而答案单调递增,所以两个队列具有单调性。

上述做法可以扩展到 n 栈的做法,时间复杂度 O(nk)

请添加图片描述

#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=1e7+10; queue<ll> q[2]; ll res; int main() { long long a,b; int k; scanf("%lld%lld%d",&a,&b,&k); q[0].push(a*b-a-b); for(int i=1;i<=k;i++) { if(!q[1].size()||q[0].front()>q[1].front()) { res=q[0].front(); q[0].pop(); q[0].push(res-a),q[1].push(res-b); } else { res=q[1].front(); q[1].pop(); q[1].push(res-b); } } printf("%lld",res); return 0; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530315.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(9)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示