CF883D Packmen Strike Back

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\n",sum(1,n),r);
	}
	return 0;
}
posted @ 2020-09-27 21:30  BILL666  阅读(269)  评论(0)    收藏  举报