[题解]CF1666E Even Split
二分答案好题。
下文中,记 Segmentland 的长度为 \(s\)。
我们先不考虑输出方案,仅考虑如何计算最小极差 \(d\)。
不难 \(d\) 具有单调性,可以二分求解。
对于每一个 \(d\),我们可以枚举区间长度的下界 \(l\),再判定能否做到所有区间的长度都在 \([l,l+d]\) 内。
我们发现这个 \(l\) 也是可以二分的,原因是我们可以 \(O(n)\) 判定当前 \(l\) 的值是偏大、偏小还是正好,可参照下面的代码:
int check(int l,int r){//长度在[l,r]内是否合法(-1为小了,0为正好,1为大了)
int x=0,y=0;//x,y存储可能的右端点范围
for(int i=1;i<=n;i++){
if(x+l>a[i+1]) return 1;
if(y+r<a[i]) return -1;
x=max(x+l,a[i]);
y=min(y+r,a[i+1]);
}
if(x>s) return 1;
if(y<s) return -1;
return 0;
}
这样我们就能在 \(O(n\log n\log s)\) 的复杂度内完成求解。
接下来考虑如何输出方案。
令第 \(i\) 个分界点的位置为 \(d_i\)(有 \(d_0=0,d_n=s\)),长度区间为 \([mn,mx]\)。
则我们的构造需要满足的条件为:
- \(d_i\in[a_i,a_{i+1}]\)
- \(d_i-d_{i-1}\in [mn,mx]\)
前面的过程保证了一定存在合法的方案,所以我们尽可能紧贴着下界构造。即:
- 先对于 \(i=1,2,\dots,n\),令 \(d_i=\max(a_i,d_{i-1}+mn)\)。
- 再对于 \(i=n,\dots,2,1\),令 \(d_{i-1}=\max(d_{i-1},d_i-mx)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int s,n,a[N],mn,mx,d[N];
int check2(int l,int r){//长度在[l,r]内是否合法(-1为小了)
int x=0,y=0;
mn=l,mx=r;
for(int i=1;i<=n;i++){
if(x+l>a[i+1]) return 1;
if(y+r<a[i]) return -1;
x=max(x+l,a[i]);
y=min(y+r,a[i+1]);
}
if(x>s) return 1;
if(y<s) return -1;
return 0;
}
bool check(int x){//x作为极差是否合法
int l=0,r=s;
while(l<=r){
int mid=(l+r)>>1,t=check2(mid,mid+x);
if(t==-1) l=mid+1;
else if(t==1) r=mid-1;
else return 1;
}
return 0;
}
void get_ans(int x){
check(x);
d[n]=s;
for(int i=1;i<n;i++) d[i]=max(d[i-1]+mn,a[i]);
for(int i=n;i;i--) d[i-1]=min(d[i-1],d[i]-mx);
for(int i=1;i<=n;i++) cout<<d[i-1]<<" "<<d[i]<<"\n";
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s>>n;
for(int i=1;i<=n;i++) cin>>a[i];
a[n+1]=s;
int l=0,r=s;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)){
r=mid;
}else{
l=mid+1;
}
}
get_ans(l);
return 0;
}
浙公网安备 33010602011771号