Codeforces 883D. Packmen Strike Back(二分+DP)
Posted nightraven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces 883D. Packmen Strike Back(二分+DP)相关的知识,希望对你有一定的参考价值。
题意:一条长为n线上有几个吃豆人和几个豆子,你可以控制吃豆人的移动方向,选定方向之后吃豆人便会一直向这个方向走,问能吃到的最大豆子数和为了达到这个目标所需的最少时间。
思路:最大的豆子数量其实就是所有的豆子,唯一有个特例就是只有一个人,有两个人以上的话,不难想出只要两人面对面走就能吃到所有豆子了,所以先处理只有一个人的情况。
void straight()
{
int sta=pos[0];
int sumlef=0,timlef=0,k=0,sumrig=0,timrig=0;
for(int i=sta-1;i>=1;i--)
{
k++;
if(s[i]=='*')
{
sumlef++;
timlef=k;
}
}
k=0;
for(int i=sta+1;i<=n;i++)
{
k++;
if(s[i]=='*')
{
sumrig++;
timrig=k;
}
}
if(sumlef<sumrig || (sumlef==sumrig && timrig<timlef))
cout<<sumrig<<" "<<timrig<<endl;
else
cout<<sumlef<<" "<<timlef<<endl;
}
然后就是怎么求最小时间了,数据范围是1e6,所以应该是O(nlogn)或O(n)的算法,再加上这道题明显符合单调性(X秒内能吃到Y颗豆子,那么X+1秒内一定可以吃到Y颗豆子)所以我们可以二分时间。
void binary()
{
int l=0,r=n,mid,ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(check(mid))
{
r=mid-1;
ans=mid;
}
else
l=mid+1;
}
cout<<sum[n]<<" "<<ans<<endl;
}
然后就是如何check的问题,我们可以用DP来解决,dp[i]表示前i个人能吃到的从左数最右边的豆子(就是前i个人能吃到的最右边的豆子),dp[i]可以从dp[i-1]再加上第i个人吃的豆子转移得到,但一共有三种情况,取最大值。
一、第i个人向左走。
二、第i个人向右走。
三、第i-1个人向右,第i个人向左。
分别算就行了,当然,为了求第i个人到第i-1个人之见豆子的数量,要提前预处理好前缀和。
void prework()
{
for(int i=1;i<=n;i++)
{
if(s[i]=='*')
sum[i]=sum[i-1]+1;
else
sum[i]=sum[i-1];
if(s[i]=='P')
pos.pb(i);
}
m=pos.size();
}
inline bool nothing(int l,int r)
{
return (r<l || !(sum[r]-sum[l-1]));
}
bool check(int tim)
{
memset(dp,0,sizeof(dp));
for(int i=0;i<m;i++)
{
if(nothing(dp[i]+1,pos[i]-tim-1))
dp[i+1]=max(dp[i+1],pos[i]);
if(nothing(dp[i]+1,pos[i]-1))
dp[i+1]=max(dp[i+1],pos[i]+tim);
if(i>=1 && nothing(dp[i-1]+1,pos[i]-tim-1) && pos[i]-tim<pos[i-1])
dp[i+1]=max(dp[i+1],pos[i-1]+tim);
}
if(nothing(dp[m]+1,n))
return 1;
return 0;
}
最后是完整的AC代码
//头文件日常省略
using namespace std;
const int maxn=1000005;
string s;
int n,m;
vector<int> pos;
int sum[maxn],dp[maxn];
void prework()
{
for(int i=1;i<=n;i++)
{
if(s[i]=='*')
sum[i]=sum[i-1]+1;
else
sum[i]=sum[i-1];
if(s[i]=='P')
pos.pb(i);
}
m=pos.size();
}
void straight()
{
int sta=pos[0];
int sumlef=0,timlef=0,k=0,sumrig=0,timrig=0;
for(int i=sta-1;i>=1;i--)
{
k++;
if(s[i]=='*')
{
sumlef++;
timlef=k;
}
}
k=0;
for(int i=sta+1;i<=n;i++)
{
k++;
if(s[i]=='*')
{
sumrig++;
timrig=k;
}
}
if(sumlef<sumrig || (sumlef==sumrig && timrig<timlef))
cout<<sumrig<<" "<<timrig<<endl;
else
cout<<sumlef<<" "<<timlef<<endl;
}
inline bool nothing(int l,int r)
{
return (r<l || !(sum[r]-sum[l-1]));
}
bool check(int tim)
{
memset(dp,0,sizeof(dp));
for(int i=0;i<m;i++)
{
if(nothing(dp[i]+1,pos[i]-tim-1))
dp[i+1]=max(dp[i+1],pos[i]);
if(nothing(dp[i]+1,pos[i]-1))
dp[i+1]=max(dp[i+1],pos[i]+tim);
if(i>=1 && nothing(dp[i-1]+1,pos[i]-tim-1) && pos[i]-tim<pos[i-1])
dp[i+1]=max(dp[i+1],pos[i-1]+tim);
}
if(nothing(dp[m]+1,n))
return 1;
return 0;
}
void binary()
{
int l=0,r=n,mid,ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(check(mid))
{
r=mid-1;
ans=mid;
}
else
l=mid+1;
}
cout<<sum[n]<<" "<<ans<<endl;
}
int main()
{
cin>>n>>s;
s=" "+s;
prework();
if(m==1)
straight();
else
binary();
return 0;
}
以上是关于Codeforces 883D. Packmen Strike Back(二分+DP)的主要内容,如果未能解决你的问题,请参考以下文章
codeforces 883H - Palindromic Cut - [字符串处理]
题解Berland.Taxi Codeforces 883L 模拟 线段树 堆