CF883D Packmen Strike Back
(problem:)
给出一个长度为N的序列,序列上每个位置或者是豆,或者是吃豆人,或者什么都没有
现在要求给每个吃豆人定向(向左吃或向右吃),定向后吃豆人会一直朝这个方向走直到尽头并吃掉沿途的所有豆
现在要求在保证尽量多的豆被吃的情况下,最小化达到这个数量所需要的时间(不一定走到尽头)
(data) (range:)
(N<=10^6)
(solution:)
此题也很妙啊
考虑如果只有一个人,那么他要么往左要么往右,判断下就行了
如果有至少两个人,那么所有豆子都可以被吃完,问题变为如何求吃完所有豆子所需要的最少时间
这个时间本身并不好求
容易发现答案具有单调性,于是我们考虑二分答案+验证
如何验证呢?
设(f_i)表示从左往右前第i个人能够吃到的最靠右的豆子的位置(注意中间不能遗漏)
那么考虑转移,有三种
- 第i个人向右走
- 第i个人向左走
- 第i个人向左走,第i-1个人向右走
分类讨论即可
(space) (time) (complexity)
(time:O(n))
(space:O(n))
(code:)
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e6+5;
int n,s[N],f[N];
char ch[N];
vector<int>p;
inline bool ok(int l,int r){return l>r?1:(s[r]-s[l-1]==0);}
inline int sum(int l,int r){return l>r?0:s[r]-s[l-1];}
inline bool ck(int x)
{
fill(f,f+n+1,0);
for(int i=1;i<=p.size();++i)
{
int pos=p[i-1];
if(ok(f[i-1]+1,pos-1))f[i]=max(f[i],pos+x);//第i个人向右走
if(ok(f[i-1]+1,pos-x-1))f[i]=max(f[i],pos);//第i个人向左走
if(i>1)
if(ok(f[i-2]+1,pos-x-1))f[i]=max(f[i],max(p[i-2]+x,pos));//第i个人向左走,第i-1个人向右走
}
return ok(f[p.size()]+1,n);
}
int main()
{
scanf("%d",&n);
scanf("%s",ch+1);
for(int i=1;i<=n;++i)
{
s[i]=s[i-1];
if(ch[i]=='P')p.pb(i);
else if(ch[i]=='*')++s[i];
}
if(p.size()==1)
{
int pos=p[p.size()-1];
int suml=sum(1,pos-1),sumr=sum(pos+1,n);
if(!suml&&!sumr)return puts("0 0"),0;
int l=1;while(ok(1,l))++l;
int r=n;while(ok(r,n))--r;
if(l>pos)return printf("%d %d",sumr,r-pos),0;
if(r<pos)return printf("%d %d",suml,pos-l),0;
if(suml==sumr)printf("%d %d",suml,min(pos-l,r-pos));
else printf("%d %d",max(suml,sumr),suml>sumr?pos-l:r-pos);
}
else
{
int l=0,r=n;
while(l+1<r)
{
int mid=l+r>>1;
ck(mid)?r=mid:l=mid;
}
printf("%d %d
",sum(1,n),r);
}
return 0;
}