更新历史

更新历史

2024-12-04

发布博客

洛谷传送门

题意

给定两个长为 nn 的序列 a,ba,b 及整数 m,k,xm,k,x,定义一轮操作为:对于所有 aiai+bi(1in)a_i\gets a_i+b_i (1\le i\le n),然后进行至多 kk 次「选择一个 ii 满足 aixa_i\ge x,令 aiaixa_i\gets a_i-x」。最小化 mm 轮操作后 aa 中的最大值。

思路

最小化最大值,容易想到二分答案,于是设问题变为判断答案不大于 gg 是否可行。

容易知道答案固定时每个下标的操作总次数是确定的,设 ii 下标被操作 cic_i 次。

我们对每个下标 ii,枚举 1jci1\le j\le c_i,计算 aia_i 的第 jj 次操作至少要几轮操作后才可以进行,将其记为 posi,jpos_{i,j}

于是原问题变为了:对于每对 i,ji,j,都要在 posi,jpos_{i,j}mm 轮中选择一轮进行操作,并且需要满足每一轮的操作次数都不超过 kk。这个问题就可以直接用贪心解决了。

直接记 tyt_y 表示有几个 posi,j=ypos_{i,j}=y。从前往后遍历 tt,维护变量 ss 表示当前最少有几个操作需要进行。则对于一个 yysmax(s+tyk,0)s\gets \max(s+t_y-k,0)。答案 gg 可行当且仅当最后 s=0s=0

那么一次 check 的复杂度为 c\sum c。注意到若 ci>m×k\sum c_i > m\times k 则必定无解,故总复杂度为 O(logV×c)O(logV×mk)O(\log V\times\sum c) \le O(\log V\times 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];
// a,b,c,t 的含义如题目或题解中所说,d 数组为第 i 个数在不进行操作的情况下,m 轮后会变成多少
inline int _ceil(int x,int y){
int g=x/y;
return g+(g*y!=x);
}
bool check(int g){
ll cnt=0; // 注意 c 数组的和可能会爆 int
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; // 一个数最后不可能在 [0,g] 中的情况
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]); // 计算 pos
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;
}