zoukankan      html  css  js  c++  java
  • [Usaco2011][bzoj2442][洛谷2527]修剪草坪解题报告(dp,贪心,单调队列)

    题目描述

    在一年前赢得了小镇的最佳草坪比赛后,Farm John变得很懒,再也没有修剪过草坪。现在,新一轮的最佳草坪比赛又开始了,Farm John希望能够再次夺冠。

    然而,Farm John的草坪非常脏乱,因此,Farm John只能够让他的奶牛来完成这项工作。Farm John有N(1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。

    靠近的奶牛们很熟悉,因此,如果Farm John安排超过K只连续的奶牛,那么,这些奶牛就会罢工去开派对:)。因此,现在Farm John需要你的帮助,计算FJ可以得到的最大效率,并且该方案中没有连续的超过K只奶牛。

    输入输出格式

    输入格式:

    第一行:空格隔开的两个整数 N 和 K

    第二到 N+1 行:第 i+1 行有一个整数 E_i

    输出格式:

    第一行:一个值,表示 Farm John 可以得到的最大的效率值。

    输入输出样例

    输入样例:
    5 2
    1
    2
    3
    4
    5
    输出样例: 
    12






    这道题有点小难,我调了一个下午才搞定。我们先来看看题面,要在一个N长的区间里,挑选区间,区间长不大于K,求最大值。

    想要直接得到正解的读者请直接看文章后半部分。
    我的第一个想法是用一个状态f[i][j]来记录,表示处理到当前数组的第i位连续使用了j个数字。
    每一个i可以递推出两个状态:使用这个数字,不使用这个数字。
    那么方程就是 f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+arr[i+1])
    于是就有了这样的代码。
    #include<iostream>
    using namespace std;
    long long dp[100001][100001];
    long long  arr[100001];
    int main()
    {
        int n,k;
        cin>>n>>k;
        for(int i=1;i<=n;i++)
        cin>>arr[i];
        dp[1][0]=arr[1];
        for(int i=1;i<=n;i++)
        {
            dp[i][0]=arr[i];
            for(int j=0;j<=k;j++)
            {
                dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+arr[i+1]);
                dp[i+1][0]=max(dp[i+1][0],dp[i][j]); 
            }
            dp[i+1][0]=max(dp[i+1][0],dp[i][10]);
        }
        long long  ans=0;
        /*for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=k;j++)
            cout<<dp[i][j]<<" ";
            cout<<endl;
        }*/
        for(int i=1;i<=k;i++)
        ans=max(ans,dp[n][i]);
        cout<<ans;
    }
    你会发现它是过不了编译的,因为dp数组实在是太大了。我适当缩小了dp的大小,于是,在洛谷上拿了50分。很明显,大的点是没办法过的。

    面对这种问题,只有一种令人崩溃的调试方法:重构代码。
    于是我又想(借鉴)了一种方程。
    用sum来存每一位的前缀和
    dp[i]=max(dp[j]+sum[i]-sum[j+1])
    j从j-K-1遍历到i-1;
    这个不太好理解
    表示的是每对一个数据,都往前考虑k个数,因为不能有连续k个数,所以在这k个数里面必须有一个数的值被删掉。这里j表示j的下一位被删了。所以我们到i时的值分为三部分,j及之前可以得到的最大值dp[j]和j+2到i的和。
    j+2到i的合就用我们维护的前缀和来处理,它的值就是sum[i]-sum[j+2-1]也就是sum[i]-sum[j+1];
    算法复杂度是O(n^2)
    但毕竟是别人家的dp方程,不至于崩吧,于是就交了,然后TLE,GG;
    方程里面,sum[i]是定值,只要求出dp[j]-sum[j+1]的最大值就行,这两个是挨着的,一路维护下来就行。忽然知道老师给我们这道题的图谋不轨了,昨天刚讲了单调队列。
    所以,这道题用单调队列来维护最大值并优化dp!!

    于是有了下面的AC代码
    注意队列必须从0开始放,不然k=1的时候,第一个数据不会被pop掉。我就因为这个细节调了一个小时。

    #include<iostream>
    #include<cstring>
    using namespace std;
    struct node{
        int size;
        long long num;
    };
    int N,K;
    node queue[100001];
    long long arr[100001];
    long long dp[100001];
    long long head,tail;
    void push(int a)
    {
        while(dp[a]-arr[a+1]>queue[tail].num&&tail>=head-1)
            tail--;
        queue[++tail].num=dp[a]-arr[a+1];
        queue[tail].size=a;
    }
    int main()
    {
        memset(queue,0,sizeof(queue));
        memset(dp,0,sizeof(dp));
        head=1;tail=0;
        cin>>N>>K;
        for(int i=1;i<=N;i++)
        {
            cin>>arr[i];
            arr[i]+=arr[i-1];
        }
        for(int i=0;i<=K;i++)
        {
            dp[i]=arr[i];
            push(i);
        }
        for(int i=K+1;i<=N;i++)
        {
            if(queue[head].size<i-K-1)
            head++;
            dp[i]=arr[i]+queue[head].num;
            push(i);
        }
        cout<<dp[N]<<endl;
    }



  • 相关阅读:
    [转载]初学C#之list
    List<>过滤重复的简单方法
    C# List<> 删除
    C# 生成随机字符串
    C#正则表达式之字符替换
    c#中怎么删除一个非空目录
    treeview 点击时选中节点
    教程链接
    iOS 允许后台任务吗?
    Git Add,Git别名等
  • 原文地址:https://www.cnblogs.com/sherrlock/p/9525786.html
Copyright © 2011-2022 走看看