zoukankan      html  css  js  c++  java
  • 单调队列+dp

    所谓烽火传递,是指在远古时代,防御外敌入侵的方法是使用烽火台报讯。烽火台又称烽燧,一般建在险要处或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情。在某两座城市之间有n个烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确地传递,在m个烽火台中至少要有一个发出信号。现输入n,m和每个烽火台发出的信号的代价,请计算总共最少需要花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确地传递。
    例如,有5个烽火台,它们发出信号的代价依次为1、2、5、6、2,且m为3,则总共最少花费的代价为4,即由第2个和第5个烽火台发出信号。

    Input

    第一行有两个数n,m分别表示n个烽火台,在m个烽火台中至少要有一个发出信号。
    第二行为n个数,表示每一个烽火台的代价。

    Output

    一个数,即最小代价。

    Samples

    Input Copy
    5 3
    1 2 5 6 2
    Output
    4

    Hint

     

    Source

     

    算法思想(DP+单调队列优化)

    f[i]表示前i座烽火台在满足条件的情况下,且点燃第i座烽火台时所需花费的最小总代价。

    思路:f[i]表示从1——i符合条件的的方案且点燃i烽火台的最小代价。

    那么可以发现i烽火台点燃那么离他最远必须点燃的是在i-m那么从i-m——i之间的最小值加上点燃i烽火台的代价就是dp[i]可得转移方程是 min(dp[j])+a[i] (i-m<=j<=i)min(dp[j])+a[i](im<=j<=i) 最后答案从i-m+1到i取最小值,这时是O(n*m)会T我们可以发现求dp[j]就是个长度为m的区间最小值,我们用单调队列去维护即可O(n)

    暴力代码:

    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        }
        memset(dp,inf,sizeof dp);
        dp[0]=0;
        dp[1]=0;
        for(int i=1;i<=n;i++){
            for(int j=i;j>=i-m&&j;j--){
                dp[i]=min(dp[j]+a[i],dp[i]);
            }
        }
        int ans=inf;
        for(int i=n-m+1;i<=n;i++)ans=min(ans,dp[i]);//cout<<dp[i]<<endl;
        printf("%d
    ",ans);
    
        return 0;
    }

    这里你要想到单调栈来维护区间最小值要维护f序列的最小值

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<map> 
    #include<string.h>
    #include <math.h>
    #include <vector>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6+100;
    int mi[maxn];
    int a[maxn];
    int q[maxn];//下标
    int dp[maxn]; 
    int main(){
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        for(int i=1;i<=m;i++){
            dp[i]=a[i];
        }
        int s=1,e=0;
        for(int i=1;i<=n;i++){//维护的一个单调递增的栈 
            if(i>m){
                dp[i]=dp[q[s]]+a[i];
            }
            while(s<=e&&dp[i]<=dp[q[e]]) e--;//维护f序列的栈 
            q[++e]=i;
            while(s<=e&&q[e]-q[s]+1>m) s++;
        }
        int ans=0x3f3f3f3f;
        for(int i=max(n-m+1,1);i<=n;i++){ 
            ans=min(ans,dp[i]);
        }
        cout<<ans<<endl;
    } 
    /*
    56 9
    100 91 269 180 173 66 288 80 265 181 156 174 172 153 171 258 228 148 142 242 33 91 132 31 51 282 228 204 16 286 106 175 129 287 237 53 154 95 271 35 163 127 236 290 53 174 279 119 12 75 111 219 103 210 33 64
    */

    AC代码二:

    #include <iostream>
    
    using namespace std;
    
    const int N = 200010;
    //f[i]表示前`i`座烽火台在满足条件的情况下,且点燃第i座烽火台时所需花费的最小总代价。
    int f[N]; 
    int w[N], q[N];
    
    int main()
    {
        int n, m;
        scanf("%d%d", &n, &m);
        
        for(int i = 1; i <= n; i ++) scanf("%d", &w[i]);
        
        f[0] = 0; //初始状态
        
        int hh = 0, tt = 0;
        q[tt] = 0; //初始状态入队
        for(int i = 1; i <= n; i ++)
        {
            //从i前面的m - 1座烽火台取最值,所以窗口大小为m - 1
            if(hh <= tt && q[hh] + m < i) hh ++;
            
            //状态计算
            f[i] = f[q[hh]] + w[i];
            
            while(hh <= tt && f[q[tt]] >= f[i]) tt --;
            q[++ tt] = i;
        }
        
        //打擂台求出最后一段区间中的最小值
        int res = 1e9;
        for(int i = n - m + 1; i <= n; i ++) res = min(res, f[i]);
        
        cout << res << endl;
        
        return 0;
    }
  • 相关阅读:
    rapidjson 使用
    【设计模式】模板方法模式
    【设计模式】策略模式
    【设计模式】建造者模式
    【设计模式】享元模式
    /dev/sda1 contains a file system with errors,check forced.
    如何编写高效的Python的代码
    VsCode 调试 Python 代码
    Python 使用 pyinstaller 打包 代码
    初次使用git上传代码到github远程仓库
  • 原文地址:https://www.cnblogs.com/lipu123/p/14310948.html
Copyright © 2011-2022 走看看