常应用于求一个固定滑动区间的最大值或者最小值。
单调队列的经典题目,在一个区间内,如果要求最小值,如果后面的数既比前面的数位置靠后,又比前面的数小,就说明前面的数已经失去价值了,剔除前面的数(如果一个选手比你小又比你强,那么你该退役了),用一个单调队列来维护,每次的答案就是队首。
#include
using namespace std;
const int N = 1e6 + 5;
const int INF = 0x3f3f3f3f;
int a[N];
int q1[N];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
int l = 1,r = 1;
q1[1] = 0;
a[0] = INF;
for(int i = 1;i <= n;i ++){
while(a[i] <= a[q1[r]] && r >= l) r --;//注意要先插入数再删除数,不然会出现奇奇怪怪的bug.
q1[++ r] = i;
while(q1[l] <= i - k) l ++;
if(i >= k) cout << a[q1[l]] << " ";
}
l = 1,r = 1;
q1[1] = 0;
a[0] = -INF;
cout << endl;
for(int i = 1;i <= n;i ++){
while(a[i] >= a[q1[r]] && r >= l) r --;
q1[++ r] = i;
while(q1[l] <= i - k) l ++;
if(i >= k) cout << a[q1[l]] << " ";
}
return 0;
}
求一下前缀和,对于一个点 i i i,我们要做的是求出 m a x ( s i − s j ) ( j > = i − m 且 j < = i − 1 ) max(s_i - s_j)(j>=i-m且j<=i-1) max(si−sj)(j>=i−m且j<=i−1),所以我们只需要维护一个长度为m的单调队列就可以了。
#include
using namespace std;
const int N = 300010;
const int INF = 0x3f3f3f3f;
int a[N],q[N],s[N];
int main(){
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
int h = 0,t = 0;
int ans = -INF;
for(int i = 1;i <= n;i ++)
s[i] = s[i - 1] + a[i];
for(int i = 0;i <= n;i ++){//注意计算答案的时候是否要保存这个数,这四句话的顺序应该有差别
while(q[h] < i - m && h < t) h ++;
if(i > 0) ans = max(s[i] - s[q[h]],ans);
while(t >= h && s[i] <= s[q[t]]) t --;
q[++ t] = i;
}
printf("%d",ans);
return 0;
}
考虑 f [ i ] f[i] f[i]为前 i i i头牛的最大方案数, f [ i ] = m a x ( f [ i − j − 1 ] + s u m [ i ] − s u m [ i − j ] ) ( j > = 1 , j < = m i n ( k , i ) . ) f[i]=max(f[i-j-1]+sum[i]-sum[i-j])(j>=1,j<=min(k,i).) f[i]=max(f[i−j−1]+sum[i]−sum[i−j])(j>=1,j<=min(k,i).)发现是一个动态过程,如果想用单调队列维护,我们发现 f [ i − j − 1 ] − s u m [ i − j ] f[i-j-1]-sum[i-j] f[i−j−1]−sum[i−j]是一个可以维护的常量,满足和后面的量无关,可以使 g [ i ] = f [ i − 1 ] − s u m [ i ] g[i]=f[i-1]-sum[i] g[i]=f[i−1]−sum[i],维护 g [ i ] g[i] g[i]的单调队列即可。
#include
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
LL f[N],s[N],q[N],g[N];
int main(){
int n,k,ans;
cin >> n >> k;
for(int i = 1;i <= n;i ++){
scanf("%d",&s[i]);
s[i] += s[i - 1];
}
int h = 0,t = 0;
for(int i = 1;i <= n;i ++){
g[i] = f[i - 1] - s[i];
while(g[i] > g[q[t]] && t >= h) t --;
q[++ t] = i;
if(q[h] < i - k) h ++;
f[i] = g[q[h]] + s[i];
}
cout << f[n];
return 0;
}
特别逆天的一道题目,希望未来还能有耐心再来做一遍,做起来感觉不难但是边界处理很恶心人。先讲顺序的思路,先破环成链,搞成一条线,设o为油的数量,d为i到i+1的距离, c [ i ] = o [ i ] − d [ i ] c[i] = o[i] - d[i] c[i]=o[i]−d[i]那么如果 c [ 1 ] + c [ 2 ] > 0 c[1]+c[2]>0 c[1]+c[2]>0,则可以从1开到3,设 s u m [ i ] sum[i] sum[i]为 c [ i ] c[i] c[i]的前缀和,那么满足能从 i i i到 j j j的条件就是 s u m [ j − 1 ] − s u m [ i − 1 ] > = 0 sum[j-1]-sum[i-1]>=0 sum[j−1]−sum[i−1]>=0,从 2 ∗ n 2*n 2∗n的位置开始往前看,处理区间内最小的 s u m [ j ] ( j > i 且 j < = i + n ) sum[j](j>i且j<=i+n) sum[j](j>i且j<=i+n)的值,如果 s u m [ j ] − s u m [ i ] > = 0 sum[j]-sum[i]>=0 sum[j]−sum[i]>=0则代表 i + 1 i+1 i+1的位置是合法的,逆时针就是反过来做一遍。
#include
using namespace std;
typedef long long LL;
const int N = 1e6;
LL s[N + 5];
int o[N + 5],dist[N + 5],q[N + 5];
bool ans[N + 5];
int main(){
int n;
cin >> n;
for(int i = 1;i <= n;i ++)
scanf("%d%d",&o[i],&dist[i]);
for(int i = 1;i <= n;i ++)
s[i] = s[i + n] = o[i] - dist[i];
for(int i = 1;i <= 2 * n;i ++)
s[i] += s[i - 1];
int hh = 0,tt = -1;
for(int i = 2 * n;i >= 0;i --){
if(q[hh] > i + n) hh ++;
if(s[q[hh]] - s[i] >= 0 && i < n) ans[i + 1] = true;
while(tt >= hh && s[i] <= s[q[tt]]) tt --;
q[++ tt] = i;
}
dist[0] = dist[n];
for(int i = 1;i <= n;i ++)
s[i] = s[i + n] = o[i] - dist[i - 1];
for(int i = 2 * n;i >= 1;i --)
s[i] += s[i + 1];
hh = 0,tt = -1;
for(int i = 1;i <= 2 * n;i ++){
if(q[hh] < i - n) hh ++;
while(tt >= hh && s[i] <= s[q[tt]]) tt --;
q[++ tt] = i;
if(i > n && s[i + 1] <= s[q[hh]]) ans[i - n] = true;
}
for(int i = 1;i <= n;i ++){
if(ans[i]) printf("TAK\n");
else printf("NIE\n");
}
return 0;
}
基于DP的一道题目,思维被之前的题目给局限住了,感觉一定要对某个值进行处理,但是我们可以对DP值进行处理,比如这道题目,我们设 f [ i ] f[i] f[i]为i点放狼烟的最大值,那么只要维护 f [ i − m + 1 ] f[i-m+1] f[i−m+1]到 f [ i − 1 ] f[i-1] f[i−1]的最小值, f [ i ] f[i] f[i]就可以处理出来,就是之前的最小值加上当前的这个值,最后处理答案以及插入先后关系的时候要特判一下,思考要更加周全。
#include
using namespace std;
const int N = 2 * 1e5;
int a[N + 5],f[N + 5],q[N + 5];
int main(){
int n,m,minn = 0x3f3f3f3f;
cin >> n >> m;
for(int i = 1;i <= n;i ++) cin >> a[i];
int hh = 0,tt = 0;
for(int i = 1;i <= n;i ++){
if(q[hh] < i - m && hh < tt) hh ++;
f[i] = f[q[hh]] + a[i];
while(f[i] < f[q[tt]] && tt >= hh) tt --;
q[++ tt] = i;
}
for(int i = n - m + 1;i <= n;i ++)
minn = min(f[i],minn);
cout << minn;
return 0;
}
和上一道问题差不多,来个二分选择一下不间断长度就可以了,不多解释。
#include
using namespace std;
const int N = 5 * 1e4;
int a[N + 5],f[N + 5],q[N + 5];
int n,t;
bool check(int x){
int hh = 0,ff = 0;
q[0] = 0;
for(int i = 1;i <= n;i ++){
f[i] = f[q[hh]] + a[i];
if(q[hh] < i - x && hh <= ff) hh ++;
while(f[i] <= f[q[ff]] && ff >= hh) ff --;
q[++ ff] = i;
}
int minn = 0x3f3f3f3f;
for(int i = n - x;i <= n;i ++)
if(f[i] <= minn) minn = f[i];
if(minn <= t) return true;
return false;
}
int main(){
cin >> n >> t;
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
int l,r,ans;
l = 0,r = n;
while(l <= r){
int mid = l + r >> 1;
if(check(mid)){
ans = mid;
r = mid - 1;
}else l = mid + 1;
}
cout << ans;
return 0;
}
感觉单调队列优化DP的题目不算很难,这道题就是跑一个二维的单调队列优化就行,详见代码。
#include
using namespace std;
const int N = 1e3;
int q[N + 5],q1[N + 5],tmp[N + 5][N + 5],tmp1[N + 5][N + 5][2];
int main(){
int a,b,n;
cin >> a >> b >> n;
for(int i = 1;i <= a;i ++)
for(int j = 1;j <= b;j ++)
scanf("%d",&tmp[i][j]);
for(int i = 1;i <= a;i ++){
int hh = 0,tt = -1,hhh = 0,ttt = -1;
q[hh] = 0,q1[hhh] = 0;
for(int j = 1;j <= b;j ++){
if(q[hh] <= j - n && hh <= tt) hh ++;
while(tmp[i][j] <= tmp[i][q[tt]] && hh <= tt) tt --;
q[++ tt] = j;
tmp1[i][j][0] = tmp[i][q[hh]];
if(q1[hhh] <= j - n && hhh <= ttt) hhh ++;
while(tmp[i][j] >= tmp[i][q1[ttt]] && hhh <= ttt) ttt --;
q1[++ ttt] = j;
tmp1[i][j][1] = tmp[i][q1[hhh]];
}
}
int minn = 0x3f3f3f3f;
for(int j = n;j <= b;j ++){
int hh = 0,tt = -1,hhh = 0,ttt = -1;
for(int i = 1;i <= a;i ++){
if(q[hh] <= i - n && hh <= tt) hh ++;
while(tmp1[i][j][0] <= tmp1[q[tt]][j][0] && tt >= hh) tt --;
q[++ tt] = i;
if(q1[hhh] <= i - n && hhh <= ttt) hhh ++;
while(tmp1[i][j][1] >= tmp1[q1[ttt]][j][1] && ttt >= hhh) ttt --;
q1[++ ttt] = i;
if(i >= n) minn = min(minn,tmp1[q1[hhh]][j][1] - tmp1[q[hh]][j][0]);
// cout << minn << " ";
}
// cout << endl;
}
cout << minn;
return 0;
}