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杨雅儒学长的考试题

  • 相关阅读:
    斯坦福大学Andrew Ng教授主讲的《机器学习》公开课观后感
    关于内推,你该知道的点点滴滴
    向大学说拜拜——大学 > 兴趣 + 时间 + 思考 + 实践
    源码浅析:InnoDB聚集索引如何定位到数据的物理位置,并从磁盘读取
    5.7.17版本mysqlbinlog实时拉取的二进制日志不完整的原因分析
    InnoDB的ibd数据文件为什么比data_length+index_length+data_free的总和还要大?
    gh-ost工具在线改表过程的详细解析
    MySQL5.7 使用utf8mb4字符集比latin1字符集性能低25%,你敢信?
    通过slow query log可以查出长时间未提交的事务吗?用实验+源码来揭晓答案
    源码浅析:MySQL一条insert操作,会写哪些文件?包括UNDO相关的文件吗?
  • 原文地址:https://www.cnblogs.com/psyyyyyy/p/10423019.html
Copyright © 2011-2022 走看看