CF1329E Dreamoon Loves AA
有个长度为\(n\)个数轴,数轴上标记了\(0,n\)以及中间的\(m\)个整点。你现在要在再标恰好\(k\)个不同的整点,最小化相邻整点间最大值-相邻整点间最小值。
\(n\le 10^{15},m\le 4*10^5,k\le n-m\)
稍微转化:相当于题目给了一堆数\(b_i\),要给这些数进行拆分,拆分\(k\)次。
显然有:如果对\(b_i\)拆分多少次,一定是尽量均分。设\(d_i\)。要求\(\sum d_i=K=k+m+1\),最小化\(\max \lceil\frac{b_i}{d_i}\rceil-\min \lfloor \frac{b_i}{d_i} \rfloor\)。
假如已经决定了答案\((L,R)\),一个必要条件是:\(\sum \lceil\frac{b_i}{R}\rceil\le K \le \sum \lfloor \frac{b_i}{L} \rfloor\)。
于是可以二分得到\(R\)的下界\(R_0\),\(L\)的上界\(L_0\)。并且发现\(L_0\le R_0\)。
再看另一个条件:\(\forall i,\exist x,L\le \lfloor \frac{b_i}{x}\rfloor \le \lceil \frac{b_i}{x}\rceil\le R\)。
把\((L_0,R_0)\)带入,得到了一些不满足限制的\(i\)。为了让这些\(i\)满足限制就需要把\(L\)缩小或\(R\)扩大。以\(L\)缩小为例,由\(\lceil \frac{b_i}{x}\rceil\le R\)得\(\lceil \frac{b_i}{R}\rceil\le x\),于是\(L\)缩小成\(\lfloor \frac{b_i}{\lceil \frac{b_i}{R}\rceil} \rfloor\)。\(R\)扩大同理。把这两个东西记作\(l_i,r_i\)。
问题变为:有个集合,集合中放着\(L_0,R_0\)。对这些\(i\),选择放\(l_i\)或\(r_i\)进入集合中。要求搞完之后集合中的极差最小。
贪心:先全部选\(l_i\),对\(l_i\)排序,每次把\(l_i\)换出来把\(r_i\)丢进去。
时间\(O(m\lg m)\)。
using namespace std;
#include <bits/stdc++.h>
#define M 400005
#define ll long long
#define du(x,y) (((x)+(y)-1)/(y))
ll n,k;
int m;
ll a[M],b[M];
ll L0,R0;
void init(){
L0=R0=-1;
ll l=1,r=n;
while (l<=r){
ll mid=l+r>>1,s=0;
for (int i=0;i<=m;++i)
s+=du(b[i],mid)-1;
if (s<=k)
r=(R0=mid)-1;
else
l=mid+1;
}
l=1,r=n;
while (l<=r){
ll mid=l+r>>1,s=0;
bool bz=1;
for (int i=0;i<=m && bz;++i){
if (b[i]/mid==0)
bz=0;
s+=b[i]/mid-1;
}
if (bz && s>=k)
l=(L0=mid)+1;
else
r=mid-1;
}
// assert(L0!=-1 && R0!=-1);
}
pair<ll,ll> p[M];
multiset<ll> s;
int main(){
freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
while (T--){
scanf("%lld%d%lld",&n,&m,&k);
for (int i=1;i<=m;++i)
scanf("%lld",&a[i]);
a[m+1]=n;
for (int i=0;i<=m;++i)
b[i]=a[i+1]-a[i];
init();
ll ans;
assert(L0<=R0);
bool bz=1;
for (int i=0;i<=m;++i)
bz&=((b[i]+R0-1)/R0<=b[i]/L0);
if (bz)
ans=R0-L0;
else{
s.clear();
s.insert(L0),s.insert(R0);
int k=0;
for (int i=0;i<=m;++i)
if (du(b[i],R0)>b[i]/L0){
p[++k]={b[i]/du(b[i],R0),du(b[i],b[i]/L0)};
s.insert(p[k].first);
}
sort(p+1,p+k+1);
ans=*s.rbegin()-*s.begin();
for (int i=1;i<=k;++i){
s.erase(s.find(p[i].first));
s.insert(p[i].second);
ans=min(ans,*s.rbegin()-*s.begin());
}
}
printf("%lld\n",ans);
}
return 0;
}