单调队列优化DP

单调队列优化DP

1089. 烽火传递


一年半之前写过的题,非常简单的状态转移方程,现在不会想了。

要善于微调状态定义。

一开始想的是 f [ i ] f[i] f[i] 表示 1 ∼ i 1\sim i 1i 的最小花费,没有定义 第 i i i 个烽火台的状态。

就会分为点燃还是不点燃。

如果是点燃的话: f [ i ] = m i n ( f [ j ] + a [ i ] ) , i − m ≤ j < i f[i] = min(f[j]+a[i]),i-m \le j<i f[i]=min(f[j]+a[i]),imj<i ;

如果不点燃:第 i i i 个烽火台可能是空段的第一个位置,第二个位置,然后就影响到后边的元素了。

然后我就不会了


看了 y 总的,直接定义 f [ i ] f[i] f[i] 表示 1 ∼ i 1\sim i 1i 的最小花费,且点燃 i i i 个烽火台。

那么状态转移方程直接就是 : f [ i ] = m i n ( f [ j ] + a [ i ] ) , i − m ≤ j < i f[i] = min(f[j]+a[i]),i-m \le j<i f[i]=min(f[j]+a[i]),imj<i ;

枚举答案就是: a n s = f [ n − m + 1 ∼ n ] m i n ans = f[n-m+1 \sim n]_{min} ans=f[nm+1n]min

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=2e5+10;

int f[N],a[N],q[N];
int n,m,hh=0,tt=-1;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    memset(f,0x3f,sizeof f);
    f[0] = 0;
    //f[i]=min(f[j]+w[i]), i-m<=j<i;
    for(int i=1;i<=n;i++){
        for(int j=max(0,i-m);j<i;j++){
            f[i] = min(f[i],f[j]+a[i]);
        }
    } 
    int res = 0x3f3f3f3f;
    for(int i=n;i>=n-m+1;i--){
        res = min(res,f[i]);
    }
    cout<<res;
    return 0;
}

用单调队列优化固定区间最小值。

这里有一个非常大的坑,以前都没有注意到

        if(hh<=tt && i-q[hh]+1>m+1)
            hh++; (1)
        f[i] = f[q[hh]] + a[i]; (2)
        while(hh<=tt && f[i] <= f[q[tt]]) (3)
            tt--;
        q[++tt] = i; (4)

考虑这四句的顺序是什么。实验证明不是这个顺序就会wa

(3)句表明 a [ i ] a[i] a[i] 打算进入单调队列中,所以 (3)之前,滑动窗口是 [ i − m , i − 1 ] [i-m,i-1] [im,i1]

(4)句,一般都是最后一句。(1)句在滑动窗口板子里边可以和(3)句交换。

所以问题就是 (2)句一般放在哪里。

滑动窗口的左边界由(1)句定义。有两种理解方式:

  1. i − i 对应的左端点 + 1 对应区间长度 i - i对应的左端点 + 1 对应区间长度 ii对应的左端点+1对应区间长度 ,如果区间长度比 要求的大 要求的大 要求的大 出队。
  2. q[hh] < i - m ;队头所在位置比 i 对应的最左边的位置还要小,表明队头在窗口外边了。

问题:如果滑动窗口是 [i-m , i - k] 那么应该怎么写?

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=2e5+10;

int f[N],a[N],q[N];
int n,m,hh=0,tt=-1;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    memset(f,0x3f,sizeof f);
    f[0] = 0;
    q[++tt] = 0;
    //f[i]=min(f[j]+w[i]), i-m<=j<i;
    for(int i=1;i<=n;i++){
        if(hh<=tt && i-q[hh]+1>m+1)
            hh++;
        f[i] = f[q[hh]] + a[i];
        while(hh<=tt && f[i] <= f[q[tt]])
            tt--;
        q[++tt] = i;
    } 
    int res = 0x3f3f3f3f;
    for(int i=n;i>=n-m+1;i--){
        res = min(res,f[i]);
    }
    cout<<res;
    return 0;
}

1090. 绿色通道


只不过是烽火传递加了一个二分答案,但是又发生了奇怪的事情。二分答案得到的左边界需要+1才能变成正确答案。吐了。

/*
A: 10min
B: 20min
C: 30min
D: 40min
*/ 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <assert.h>
#include <sstream>
#define pb push_back 
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
template<typename T1,typename T2>
ostream& operator<<(ostream& os,const map<T1,T2>&v){for(auto c:v)os<<c.first<<" "<<c.second<<endl;return os;}
template<typename T>inline void rd(T &a) {
    char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
    while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}

typedef pair<long long ,long long >PII;
typedef pair<long,long>PLL;

typedef long long ll;
typedef unsigned long long ull; 
const int N=5e4+10,M=1e9+7;

int n,m,a[N];
int f[N];
int q[N],hh,tt=-1;
bool check(int mid){
    fo(i,1,n)f[i] = 0x3f3f3f3f;
    hh = 0,tt = 0;
    f[0] = 0;
    for(int i=1;i<=n;i++){  
        if(hh<=tt && q[hh] < i-mid-1)
            hh++;
        f[i] = f[q[hh]] + a[i];
        while(hh<=tt && f[i] <= f[q[tt]])
            tt--;
        q[++tt] = i;
    }
    for(int i=n-mid;i<=n;i++){
        if(f[i]<=m)return 1;
    }
    return 0;
}

void solve(){
    cin>>n>>m;
    fo(i,1,n)cin>>a[i];
    int l = 0, r = n;
    while(l<r){
        int mid = l+r>>1;
        if(check(mid))r = mid;
        else l = mid+1;
    }
    cout<<r<<endl;
}

int main(){
    solve();
    return 0;
}

琪露诺

题目描述

在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。

某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。

小河可以看作一列格子依次编号为 0 0 0 N N N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子 i i i 时,她只移动到区间 [ i + L , i + R ] [i+L,i+R] [i+L,i+R] 中的任意一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。

每一个格子都有一个冰冻指数 A i A_i Ai,编号为 0 0 0 的格子冰冻指数为 0 0 0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数 A i A_i Ai。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。

但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。

开始时,琪露诺在编号 0 0 0 的格子上,只要她下一步的位置编号大于 N N N 就算到达对岸。

输入格式

第一行三个正整数 N , L , R N, L, R N,L,R

第二行共 N + 1 N+1 N+1 个整数,第 i i i 个数表示编号为 i − 1 i-1 i1 的格子的冰冻指数 A i − 1 A_{i-1} Ai1

输出格式

一个整数,表示最大冰冻指数。

样例 #1

样例输入 #1

5 2 3
0 12 3 11 7 -2

样例输出 #1

11

提示

对于 60 % 60\% 60% 的数据, N ≤ 1 0 4 N \le 10^4 N104

对于 100 % 100\% 100% 的数据, N ≤ 2 × 1 0 5 N \le 2\times 10^5 N2×105,$-10^3 \le A_i\le 10^3 $,$1 \le L \le R \le N $。数据保证最终答案不超过 2 31 − 1 2^{31}-1 2311


巨折磨的一道题。

详情看第一篇题解。

学会处理右端点和 i 有间隔的滑动窗口

f [ i ] = m a x ( f [ j ] ) + A [ i ]   ( i − R ≤ j ≤ i − L ) f[i]=max(f[j])+A[i] \ (i−R≤j≤i−L) f[i]=max(f[j])+A[i] (iRjiL)

/*
A: 10min
B: 20min
C: 30min
D: 40min
*/ 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <assert.h>
#include <sstream>
#define pb push_back 
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
template<typename T1,typename T2>
ostream& operator<<(ostream& os,const map<T1,T2>&v){for(auto c:v)os<<c.first<<" "<<c.second<<endl;return os;}
template<typename T>inline void rd(T &a) {
    char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
    while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}

typedef pair<long long ,long long >PII;
typedef pair<long,long>PLL;

typedef long long ll;
typedef unsigned long long ull; 
const int N=3e5+10,M=1e9+7;

int n,l,r;
ll a[N],f[N];
int q[N],hh,tt;
void solve(){
    cin>>n>>l>>r;
    fo(i,0,n)cin>>a[i];
	memset(f,0xcf,sizeof f);
	f[0] = 0;
	// f[i]表示跳到第i格能获得的最大值
    ll ans = -0x3f3f3f3f;
    for(int i=l;i<=n;i++){	
    
    	while(hh<=tt && f[i-l] >= f[q[tt]])
    		tt--;
    	q[++tt] = i-l;
    	
    	if(hh<=tt && q[hh] < i-r)
    		hh++;
    	f[i] = f[q[hh]] + a[i];
    	if(i+r>n)
    		ans = max(ans,f[i]);
    }
    cout<<ans;
}

int main(){
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值