该题在DP的题单里,但却没有想出用DP的解法。题解里也基本都是二分的解法。
但是大部分的题解的特判特别多,这边用了思路比较简单的贪心方法。贪心方式绝对没有问题,虽然本人没有证明出来,但理解后就会发现思路比较简单,代码短,且一定正确。
1.首先对每个食物'*'找到它后面最近的人'P'所在的位置,若没有的话那么位置记为0.存在post数组中。
2.二分答案,记为mid。主要是check如何判断。
3.从前向后遍历,会有两种情况:
1->如果先遇到食物'*',那么说明post[i]一定要吃掉该食物(因为前面没有P),
那么此处就要算一下能跑的最大距离:
(1)P先跑到i再尽力向后跑,那么往后跑的最远距离为post[i]+mid-(post[i]-i)*2
(2)P先向后跑一段,再跑到i位置,当然不用再跑到i前面的位置了,因为没有食物'*'在i的前面了。那么能尽力向后跑的那一段为post[i]+(mid-post[i]+i)/2
上面那两个值取最大记在r中,表示1~r里面的食物都已经被当前这个P吃掉了。并把这个P标记已经用过。
然后继续往后跑,对于1~r段中的‘*’都不用再去管了,只用管r+1~n中的食物‘*’即可。但1~r段中剩下没被标记过的P要尽力往后跑mid段距离(不用考虑往前跑了,因为前面的食物已经没了),并更新r=max(r,i+mid),表示1~r段的食物都吃掉了
也就是说只用对r+1~n这段继续上述的操作即可。
2->如果先遇到P,那么直接更新r=max(r,i+mid),表示1~r的食物已经吃完了。
然后对r+1~n段继续上述操作
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+50;
int n;
char s[maxn];
int post[maxn];
bool st[maxn];
void init(){
for(int i=n,pos=0;i>=1;i--){
if(s[i]=='P') pos=i;
if(s[i]=='*') post[i]=pos;
}
}
bool check(int mid){
for(int i=1;i<=n;i++) st[i]=false;
for(int i=1,r=-1;i<=n;i++){//pos表示第一个*的位置
if(s[i]=='P'&&!st[i]) r=max(r,i+mid);
if(s[i]=='*'&&i>r){
if(!post[i]||post[i]-i>mid) return false;
int d1=post[i]+mid-(post[i]-i)*2,d2=post[i]+(mid-post[i]+i)/2;
st[post[i]]=true;
r=max(r,max(d1,d2));
}
}
return true;
}
void solve(){
cin>>n>>s+1;init();
int l=1,r=maxn*2,ans;
while(l<=r){
int mid=l+r>>1;
if(check(mid)){
r=mid-1,ans=mid;
}
else l=mid+1;
}
cout<<ans<<endl;
}
int main(){
int t;t=1;
while(t--) solve();
return 0;
}