zoukankan      html  css  js  c++  java
  • 【单调队列优化dp】 分组

    【单调队列优化dp】 分组

    >>>>题目

    【题目】

    给定一行n个非负整数,现在你可以选择其中若干个数,但不能有连续k个数被选择。你的任务是使得选出的数字的和最大

    【输入格式】

    第一行两个整数 n,k,如题目描述
    接下来一行n 个数,表示这个序列

    【输出格式】

    输出一行一个数,表示最大的和

    【输入样例】

    5 2
    1 2 3 4 5

    【输出样例】

    12

    【数据范围与约定】

    对于20%的数据,保证1 <=n <=10。

    对于40%的数据,保证1 <=n <=200。

    对于60%的数据,保证1 <=n <=100000。

    对于100%的数据,保证1 <=n <=2000000,1<=K<=n。

    >>>>分析

    因为题目给出非负整数,根据贪心,我们尽量多选择长度为k的序列

    数据范围很大,暴力求每一段的和的做法肯定会T掉,那么考虑dp

    题目涉及到求区间和,我们预处理出前缀和,用O(1)复杂度求出区间和

    定义:dp[i]表示前i个人的最大收益,sum[i]表示前缀和

    现在选的这一段区间和用sum[i]-sum[j]表示(i-k<=j<=i)

    这段区间与  前一段长为k的区间  中间要空一个数(不能连续选k+1个数),于是我们固定i点,不断地枚举j点,找到和的最大值

    因为sum求的是闭区间的前缀和 , sum[i]-sum[i]表示区间[ j+1 ,i ]的和,中间空的数就是j,再加上dp[j-1]就可以更新dp[i]的值

    综上,我们可以得到状态转移方程

    dp[i]=max( dp[j-1]+sum[i]-sum[j])  (i-k<=j<=i)

    这里的max是对应每一个j点算出括号里的值,再求最大值

    那么又怎么算最大值呢?总不能真的枚举j再找最大值吧?肯定不行,时间复杂度太高

    但是你会突然发现

    原方程可以拆分成 dp[i]=max(dp[j-1]-sum[j])+sum[i]

    然后我们可以发现这个式子的变化与sum[i]无关,因为我们固定了i点,只是j点在变

    那么我们考虑一个超棒的家伙——单调队列优化

    (注意一下单调队列里面存的是下标)

    定义f[j]=dp[j-1]-sum[j],将f[j]丢进单调队列里面,每次取出队首元素

    扩展下一个点的时候,更新一下:f[i+1]=dp[i]-sum[i+1](和上面的式子是一样的),将它丢进单调队列里面就好啦

    ヾ(๑╹◡╹)ノ"

    >>>>代码

    #include<bits/stdc++.h>
    #define maxn 2000005
    #define ll long long
    using namespace std;
    int head=0,tail=1,n,k;
    ll dp[maxn],f[maxn],sum[maxn];
    int a[maxn],q[maxn];
    int read()
    {
        int x=0 ; char c=getchar() ;
        while(c<'0'||c>'9') c=getchar() ;
        while(c>='0'&&c<='9') { x=x*10+c-'0', c=getchar() ; }
        return x;
    }
    int main()
    {
    //    freopen("group.in","r",stdin);
    //    freopen("group.out","w",stdout);
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) 
        {
            a[i]=read();
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=1;i<=n;i++)
        {
            while(head<tail&&i-q[head]>k) head++;//如果队首的元素已经不在范围里,踢出队列 
            dp[i]=f[q[head]]+sum[i];//取出队首元素,更新dp[i]的值 
            f[i+1]=dp[i]-sum[i+1];//更新 f[i+1]
            while(head<tail&&f[q[tail-1]]<f[i+1]) tail--;//维护单调队列,将小于f[i+1]的数都踢出去 
            q[tail++]=i+1;
        }
        printf("%I64d",dp[n]);
        return 0;
    }

    >>>>总结

    通过这道题目,我们还可以知道

    对于一类dp,状态转移方程抽象为:dp[i]=max(f[j])+g[i]   (l[i]<=j<=r[i])

    并且l[i]~r[i]单调不减时,我们都可以考虑用单调队列优化

    步骤:踢出过时元素,更新dp值,新元素入队并且维护单调队列

    那么就到这里啦!

    完结撒花٩(๑❛ᴗ❛๑)۶

    题目来源: 2019.2.19杨雅儒学长的考试题

  • 相关阅读:
    Dynamics AX 2012 R2 配置E-Mail模板
    Dynamics AX 2012 R2 设置E-Mail
    Dynamics AX 2012 R2 为运行失败的批处理任务设置预警
    Dynamics AX 2012 R2 耗尽用户
    Dynamics AX 2012 R2 创建一个专用的批处理服务器
    Dynamics AX 2012 R2 创建一个带有负载均衡的服务器集群
    Dynamics AX 2012 R2 安装额外的AOS
    Dynamics AX 2012 R2 将系统用户账号连接到工作人员记录
    Dynamics AX 2012 R2 从代码中调用SSRS Report
    Dynamics AX 2012 R2 IIS WebSite Unauthorized 401
  • 原文地址:https://www.cnblogs.com/psyyyyyy/p/10423019.html
Copyright © 2011-2022 走看看