P10806 [CEOI 2024] 洒水器
看到这个题目,很明显可以二分答案。
考虑二分长度后怎么检验是否可行。
最简单的贪心思路就是直接从左到右枚举,如果左边有就向左边,否则向右边。
但是这样显然有错误,有时会存在左边的一个向左,右边的一个也向左,但是右边的也把左边所要覆盖的包含了,那么左边的如果向右就可能能够覆盖更多了。
显然局部最优解不一定属于全局最优解,但是考虑的内容并不多,考虑能否反悔。
这启示我们在向左时,要先看看之前选择了什么。
我们把合法状态设置为目前左边的全部覆盖且最右端是洒水器的情况。
我们希望合法状态覆盖的位置尽量向右。
如果最右边洒水器的都无法解决距离上一个洒水器中间的部分,那么显然不合法。
每加入一些需要浇水的地方和一个洒水器,那么我们结合前面洒水器的最右洒水距离,得出是否有还没浇到的。
如果没有,那么我们直接让现在的这个洒水器向右。
否则,就必须向左。
那么为了最向右,我们考虑上一个能不能改变方向去向右。
如果可以或者本来就是,那么我们就选择让这个向右。
那为什么不把上上个改成向右呢?
因为如果我们上个无法修改成向右,因为在不考虑上上个到上个之间的点时是保证有方案的,那么就说明当前的向左无法满足前一个要浇的点,那么就更不可能满足上上个。
并且我们修改了上一个,上上个就没有修改的必要了。
因此我们就可以选择判断是否修改上一个。
但是如果上一个修改了上上个怎么办?
那么就让上上个改回来,改回来以后能够保证有解,同时保证当前最大。
简单来说,上一个被改了,那么我们就让上上个之前保持原本的最佳状况即可。
至于维护,就直接打上标记,然后从后往前遍历,如果这个点有标记,那么把前一个位置标记去掉并修改。
这样最后就能得到最优方案了。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
char c=getchar();int x=0;bool f=0;
while(c>'9'||c<'0'){
if(c=='-')f=1;
c=getchar();
}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(f)return -x;
return x;
}
int n,m,s[100005],f[100005],pre[100005];
bool ans[100005],mark[100005];
bool check(int x){
memset(ans,0,sizeof(ans));
memset(mark,0,sizeof(mark));
int w=1;
for(int i=1;i<=n;i++){
if(w<=m&&f[w]<s[i]){
if(f[w]<s[i]-x)return false;
pre[i]=f[w];
while(w<=m&&f[w]<=s[i])w++;
if(i>1&&!ans[i-1]&&pre[i-1]>=s[i]-x){
mark[i]=1;
while(w<=m&&f[w]<=s[i-1]+x)w++;
}
}
else {
while(w<=m&&f[w]<=s[i]+x)w++;
ans[i]=1;
}
}
if(w<=m)return false;
for(int i=n;i>=2;i--)if(mark[i])mark[i]=mark[i-1]=0,ans[i-1]=1;
return true;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)s[i]=read();
for(int i=1;i<=m;i++)f[i]=read();
int l=0,r=1e9,res=-1;
while(l<=r){
int mid=l+r>>1;
if(check(mid))r=mid-1,res=mid;
else l=mid+1;
}
if(res==-1){
cout<<-1;
return 0;
}
check(res);
cout<<res<<endl;
for(int i=1;i<=n;i++){
if(ans[i])putchar('R');
else putchar('L');
}
return 0;
}

浙公网安备 33010602011771号