更新历史
洛谷传送门
题意
给定两个长为 n 的序列 a,b 及整数 m,k,x,定义一轮操作为:对于所有 ai←ai+bi(1≤i≤n),然后进行至多 k 次「选择一个 i 满足 ai≥x,令 ai←ai−x」。最小化 m 轮操作后 a 中的最大值。
思路
最小化最大值,容易想到二分答案,于是设问题变为判断答案不大于 g 是否可行。
容易知道答案固定时每个下标的操作总次数是确定的,设 i 下标被操作 ci 次。
我们对每个下标 i,枚举 1≤j≤ci,计算 ai 的第 j 次操作至少要几轮操作后才可以进行,将其记为 posi,j。
于是原问题变为了:对于每对 i,j,都要在 posi,j 至 m 轮中选择一轮进行操作,并且需要满足每一轮的操作次数都不超过 k。这个问题就可以直接用贪心解决了。
直接记 ty 表示有几个 posi,j=y。从前往后遍历 t,维护变量 s 表示当前最少有几个操作需要进行。则对于一个 y,s←max(s+ty−k,0)。答案 g 可行当且仅当最后 s=0。
那么一次 check
的复杂度为 ∑c。注意到若 ∑ci>m×k 则必定无解,故总复杂度为 O(logV×∑c)≤O(logV×mk)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=10005; int n,m,k,x; int a[N],b[N],c[N],d[N],t[N];
inline int _ceil(int x,int y){ int g=x/y; return g+(g*y!=x); } bool check(int g){ ll cnt=0; for(int i=1;i<=n;i++) if(d[i]>g){ c[i]=_ceil(d[i]-g,x); if(d[i]-c[i]*x<0) return 0; cnt+=c[i]; } if(cnt>m*k) return 0; for(int i=1;i<=m;i++) t[i]=0; for(int i=1;i<=n;i++) if(d[i]>g){ for(int j=1;j<=c[i];j++){ int pos=1; if(b[i]&&a[i]<j*x) pos=_ceil(j*x-a[i],b[i]); t[pos]++; } } int s=0; for(int i=1;i<=m;i++) s=max(s+t[i]-k,0); return s==0; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); cin>>n>>m>>k>>x; for(int i=1;i<=n;i++){ cin>>a[i]>>b[i]; d[i]=a[i]+b[i]*m; } int l=0,r=1.1e8,mid,ans=0; while(l<=r){ mid=(l+r)>>1; if(check(mid)) r=mid-1,ans=mid; else l=mid+1; } cout<<ans<<"\n"; return 0; }
|