1. 程式人生 > >Codeforces 883D. Packmen Strike Back(二分+DP)

Codeforces 883D. Packmen Strike Back(二分+DP)

using lse com string not 所有 vector i+1 得到

傳送門


題意:一條長為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)