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;
}

原文地址:https://www.cnblogs.com/NightRaven/p/9459741.html

时间: 2024-11-09 00:29:43

Codeforces 883D. Packmen Strike Back(二分+DP)的相关文章

codeforces 289B - Polo the Penguin and Matrix 二分+dp

题意:给你一个序列,每一次可以对序列里面任意数+d 或者 -d 问你最少多少步能够使得数列里面所有的数相等 解题思路:从 1 - 10000 枚举这个数,二分找数列中小于等于它的最大的那个数,然后求前缀和以后刻意快速求出差值和的绝对值,差值和/d 就是我们所求数. 解题代码: 1 // File Name: 289b.cpp 2 // Author: darkdream 3 // Created Time: 2014年07月29日 星期二 22时33分11秒 4 5 #include<vecto

CodeForces 540D Bad Luck Island 概率dp

CodeForces 540D 应该是简单概率dp,由于写得少显得十分蠢萌 求期望逆推,求概率正推,大概是这么个意思,贴一发留恋 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define db double const int maxn=108; db dp[maxn][maxn][maxn]; int main() { int i,j,n,m,k,p; whi

codeforces 148E Aragorn&#39;s Story 背包DP

Aragorn's Story Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/problemset/problem/148/E Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of enemies who

CodeForces 21D Traveling Graph 状压dp+欧拉回路

题目链接:点击打开链接 题意: 给定n个点m条边的无向图 求从1点开始经过每条边至少一次最后回到1点的最小路程 显然就是找一条路径可重复的欧拉回路 思路: 首先对于欧拉回路的结论是:所有点的度数都为偶数 因为所有边至少经过一次,那么可以把题意转换成加最少多少条边使得图满足以上结论 而加的边目的是为了把奇度数转成偶度数,先floyd一下得到任意点间加边的最小花费 dp[i]表示状态i下度数都为偶数的最小花费. 状压dp,把i状态下,所有未选择的点中挑2个奇度数的转移即可. #include <cs

二分+DP HDU 3433 A Task Process

HDU 3433 A Task Process Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1368    Accepted Submission(s): 684 Problem Description There are two kinds of tasks, namely A and B. There are N workers

【bzoj1044】[HAOI2008]木棍分割 二分+dp

题目描述 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007... 输入 输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,1000),1<=Li<=1000. 输出 输出有2个数,

Codeforces 67C Sequence of Balls 编辑距离 dp

题目链接:点击打开链接 有一个交换操作比较特殊,所以记录每个点距离自己最近的那个字符的位置 然后交换就相当于把第一行要交换的2个字符 之间的字符都删掉 把第二行要交换的2个字符 之间的字符都插入第一行的2个字符之间 然后再进行交换. #include <cstdio> #include <cstring> #include<iostream> using namespace std; #define inf 10000000 #define N 4005 #define

Codeforces 847E - Packmen

847E - Packmen 思路:二分时间. 代码: #include<bits/stdc++.h> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) int n; string s; bool check(int t) { int now=-1,bean=-2;//now是上一个P覆盖到的位置,bean是没有被上一个P覆盖到的第一个星星

Codeforces 374D Inna and Sequence 二分+树状数组

题目链接:点击打开链接 给定n个操作,m长的序列a 下面n个数 if(co>=0)则向字符串添加一个co (开始是空字符串) else 删除字符串中有a的下标的字符 直接在序列上搞,简单模拟 #include<stdio.h> #include<iostream> #include<string.h> #include<set> #include<vector> #include<map> #include<math.h&