单调队列优化DP
1089. 烽火传递
一年半之前写过的题,非常简单的状态转移方程,现在不会想了。
要善于微调状态定义。
一开始想的是 f [ i ] f[i] f[i] 表示 1 ∼ i 1\sim i 1∼i 的最小花费,没有定义 第 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]),i−m≤j<i ;
如果不点燃:第 i i i 个烽火台可能是空段的第一个位置,第二个位置,然后就影响到后边的元素了。
然后我就不会了
看了 y 总的,直接定义 f [ i ] f[i] f[i] 表示 1 ∼ i 1\sim i 1∼i 的最小花费,且点燃第 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]),i−m≤j<i ;
枚举答案就是: a n s = f [ n − m + 1 ∼ n ] m i n ans = f[n-m+1 \sim n]_{min} ans=f[n−m+1∼n]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] [i−m,i−1]
(4)句,一般都是最后一句。(1)句在滑动窗口板子里边可以和(3)句交换。
所以问题就是 (2)句一般放在哪里。
滑动窗口的左边界由(1)句定义。有两种理解方式:
- i − i 对应的左端点 + 1 对应区间长度 i - i对应的左端点 + 1 对应区间长度 i−i对应的左端点+1对应区间长度 ,如果区间长度比 要求的大 要求的大 要求的大 出队。
- 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 i−1 的格子的冰冻指数 A i − 1 A_{i-1} Ai−1。
输出格式
一个整数,表示最大冰冻指数。
样例 #1
样例输入 #1
5 2 3
0 12 3 11 7 -2
样例输出 #1
11
提示
对于 60 % 60\% 60% 的数据, N ≤ 1 0 4 N \le 10^4 N≤104。
对于 100 % 100\% 100% 的数据, N ≤ 2 × 1 0 5 N \le 2\times 10^5 N≤2×105,$-10^3 \le A_i\le 10^3 $,$1 \le L \le R \le N $。数据保证最终答案不超过 2 31 − 1 2^{31}-1 231−1。
巨折磨的一道题。
详情看第一篇题解。
学会处理右端点和 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] (i−R≤j≤i−L)
/*
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;
}