P10806 [CEOI 2024] 洒水器 题解
P10806 [CEOI 2024] 洒水器 题解
思路
首先可以想到二分答案,难点在于如何判断合法。
首先考虑贪心,如果洒水器前面还有花没浇则向左转,否则向右转。
贪心代码1 check 部分
bool check(int k){int p=1;for(int i=1;i<=n&&p<=m;i++){if(f[p]<s[i]){if(s[i]-k>f[p])return 0;while(f[p]<=s[i]&&p<=m)p++;}else{while(f[p]<=s[i]+k&&p<=m)p++;}}return p>m;
}
提交之后会获得 9 分的高分,考虑如何优化贪心。
注意到以下情况:
按照原本的贪心思路,当喷水强度为 3 时,两个洒水器都会向左旋转,第三个花无法被浇到,就像下面这张图片显示的那样。
此时如果我们将第一个洒水器向右旋转,发现可以浇完全部的花,就像下面这张图片显示的那样。
那么是什么时候会向右旋转呢?注意到第二个洒水器如果向左旋转,那么原本第一个洒水器需要浇的花已经被第二个洒水器浇了,所以第一个洒水器就可以向右旋转了。
贪心代码2 check 部分
bool check(int k){int p=1;for(int i=1;i<=n&&p<=m;i++){if(f[p]<s[i]){pre[i]=f[p];ans[i]='L';if(s[i]-k>f[p])return 0;while(f[p]<=s[i]&&p<=m)p++;if(ans[i-1]=='L'&&pre[i-1]>=s[i]-k){pre[i]=pre[i-1];ans[i-1]='R';while(f[p]<=s[i-1]+k&&p<=m)p++;}}else{ans[i]='R';while(f[p]<=s[i]+k&&p<=m)p++;}}return p>m;
}
交上去发现只有 73 分,这是为什么呢?
注意到有一组 hack:
3 5
1 8 13
0 7 9 10 15
你输出的可能是正确答案 7,但是如果 check 一下答案 8,会返回 0,这又是为什么呢?
因为当喷水强度为 8 时,第二个洒水器就会让第一个洒水器向右旋转,最终无法浇到全部的花。
假如有一段连续往左旋转的洒水器:
LLLLLLLLLLLLLLL
则倒数第一和倒数第二个洒水器之间一定有一个洒水器不能被倒数第三个洒水器向右旋转覆盖到(否则倒数第一个洒水器就会向左旋转),此时将倒数第二个洒水器向右旋转是最优的,因为如果第二个洒水器向左旋转,倒数第一个洒水器也需要向左旋转,就无法浇到右边的花了。
以此类推,我们需要从后往前找可以向右旋转的洒水器即可。
贪心代码3 check 部分
bool check(int k){int p=1;for(int i=1;i<=n&&p<=m;i++){if(f[p]<s[i]){//如果左边还有花需要浇pre[i]=f[p];ans[i]='L';if(s[i]-k>f[p]){return 0;}while(f[p]<=s[i]&&p<=m)p++;//该洒水器向左旋转可以浇到的花if(ans[i-1]=='L'&&pre[i-1]>=s[i]-k){//如果有多个连续的Lvis[i]=1;//此处仅做记录,后面再统一处理连续的Lwhile(f[p]<=s[i-1]+k&&p<=m)p++;//要么该洒水器向右旋转,要么前一个洒水器向右旋转,至少会浇到s[i-1]+k的花}}else{ans[i]='R';while(f[p]<=s[i]+k&&p<=m)p++;}}for(int i=n;i>=1;i--)if(vis[i]){vis[i]=vis[i-1]=0;//注意不能有两个连续的L变Rans[i-1]='R';}return p>m;
}
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,s[100010],f[100010],pre[100010],ans[100010],vis[100010];
bool check(int k){int p=1;for(int i=1;i<=n&&p<=m;i++){if(f[p]<s[i]){pre[i]=f[p];ans[i]='L';if(s[i]-k>f[p]){return 0;}while(f[p]<=s[i]&&p<=m)p++;if(ans[i-1]=='L'&&pre[i-1]>=s[i]-k){vis[i]=1;while(f[p]<=s[i-1]+k&&p<=m)p++;}}else{ans[i]='R';while(f[p]<=s[i]+k&&p<=m)p++;}}for(int i=n;i>=1;i--)if(vis[i]){vis[i]=vis[i-1]=0;ans[i-1]='R';}return p>m;
}
int main(){ios::sync_with_stdio(false);cin.tie(0);cin>>n>>m;for(int i=1;i<=n;i++){cin>>s[i];ans[i]='R';}for(int i=1;i<=m;i++){cin>>f[i];}int l=0,r=1e9;while(l<r){int mid=(l+r)>>1;if(check(mid))r=mid;else l=mid+1;}if(check(r))cout<<r<<'\n';else{cout<<-1;return 0;} for(int i=1;i<=n;i++){cout<<(char)ans[i];}return 0;
}