• 动态规划中的单调队列优化


    最近经常出现单调队列,斜率优化的题目。看到周围的大神们都会做了,我只能跟上去。
    要慢慢来,先学单调队列。

    什么类型的DP需要用到常规的单调队列?

    类似这样的转移方程可以用到单调队列:

    f[i]=max(g[j])+w[i]

    其中,g[j]是一个与i无关系的数。w[i]只与i有关系。

    怎么用?

    我们首先开一个队列。DP时:
    1、先删掉前面超出范围的队头。
    2、利用队头转移。
    3、将这个数和队尾比较,若队尾不比它优,就删掉队尾,直到队列为空或队尾比它优。最后将它加进队尾。

    原因

    1、单调队列中的数都在要范围之内。
    2、队头最优(不然早被后面的删掉了)。
    3、为什么不只存队头?因为队头比较老,若超出范围就要被删掉。


    例题 JZOJ 1771. 【NOIP动态规划专题】烽火传递

    Description
      烽火台又称烽燧,是重要的军事防御设施,一般建在险要或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情,在某两座城市之间有n个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续m个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。
    Input
      第一行:两个整数N,M。其中N表示烽火台的个数,M表示在连续m个烽火台中至少要有一个发出信号。接下来N行,每行一个数Wi,表示第i个烽火台发出信号所需代价。
    Output
      一行,表示答案。
    Sample Input
    5 3
    1
    2
    5
    6
    2

    Sample Output
    4

    Data Constraint
    对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤ 100,000,Wi≤100。

    在单调队列的题中,这是一道水题了。
    设f[i]表示i必须选时最小代价。
    初值:
    f[0]=0 f[1..n]=∞
    方程:
    f[i]=min(f[j])+w[i]
    并且max(0,i-m)<=j<i
    为什么j有这样的范围?如果j能更小,那么j~i这段区间中将有不符合条件的子区间,就会错。应保证不能有缝隙。
    最后在f[n-m+1..n]中取最小值即答案
    时间复杂度O(nm)

    没有单调队列的代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define I_O(x) freopen(""#x".in","r",stdin);freopen(""#x".out","w",stdout)
    int n,m;
    int w[100001];
    int f[100001];
    int main()
    {
        I_O(beacon);
        scanf("%d%d",&n,&m);
        int i,j;
        for (i=1;i<=n;++i)
            scanf("%d",&w[i]);
        memset(f,127,sizeof f);
        f[0]=0;
        for (i=1;i<m;++i)
        {
            for (j=0;j<i;++j)//分两段写的原因是为了卡常——你也可以并在一起,然后j的初值为max(0,i-m)
                f[i]=min(f[i],f[j]);
            f[i]+=w[i];
        }
        for (i=m;i<=n;++i)
        {
            for (j=i-m;j<i;++j)
                f[i]=min(f[i],f[j]);
            f[i]+=w[i];
        }
        int ans=0x7f7f7f7f;
        for (i=n-m+1;i<=n;++i)
            ans=min(ans,f[i]);
        printf("%d
    ",ans);
    }

    我们发现这个方程可以以单调队列实现,用单调队列存好最优决策点,就可以将时间复杂度从O(nm)降到O(n)。

    正解代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define I_O(x) freopen(""#x".in","r",stdin);freopen(""#x".out","w",stdout)
    int n,m;
    int w[100001];
    int que[100001],head=0,tail=0;
    int f[100001];
    int main()
    {
        I_O(beacon);
        scanf("%d%d",&n,&m);
        int i,j;
        for (i=1;i<=n;++i)
            scanf("%d",&w[i]);
        memset(f,127,sizeof f);
        f[0]=0;
        que[0]=0;
        for (i=1;i<=n;++i)
        {
            if (que[head]<i-m)
                ++head;//将超出范围的队头删掉
            f[i]=f[que[head]]+w[i];//转移(用队头)
            while (head<=tail && f[que[tail]]>f[i])
                --tail;//将不比它优的全部删掉
            que[++tail]=i;//将它加进队尾
        }
        int ans=0x7f7f7f7f;
        for (i=n-m+1;i<=n;++i)
            ans=min(ans,f[i]);
        printf("%d
    ",ans);
    }
  • 相关阅读:
    java拦截器与过滤器打印请求url与参数
    mybatis学习笔记(六)使用generator生成mybatis基础配置代码和目录结构
    【IDEA】IDEA创建Maven的Web项目并运行以及打包
    【环境变量】Linux 下三种方式设置环境变量与获取环境变量
    【Git】GitHub的SSH提交配置[
    spring配置redis注解缓存
    【查看linux配置】查看linux系统常用的命令,Linux查看系统配置常用命令
    Redis集群
    linux中wget 、apt-get、yum rpm区别
    spring+redis的集成,redis做缓存
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145308.html
Copyright © 2020-2023  润新知