单调队列小专题
- 概念题
滑动窗口
模板题,维护一个值和一个下标即可
Luogu2216
首先对每行做一遍滑动窗口,然后发现其实列也是个滑动窗口
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
#define int LL
const int inf = 1e9, INF = 0x3f3f3f3f,maxn=1005;
int n,m,k,a[maxn][maxn];
int mx[maxn][maxn], mx2[maxn][maxn];
int mn[maxn][maxn], mn2[maxn][maxn];
namespace s1{
int qu[maxn],qu2[maxn],qu3[maxn],qu4[maxn];
void solve(){
for(int i=1;i<=n;i++){
int hd=1, tl=0;
for(int j=1;j<=m;j++){
while(hd <= tl && qu[tl] <= a[i][j])-- tl;
qu[++ tl] = a[i][j];
qu2[tl] = j;
if(j < k)continue;
while(hd <= tl && j - qu2[hd] >= k) ++ hd;
mx[i][j - k + 1] = qu[hd];
}
}
for(int j=1;j<=m-k+1;j++){
int hd=1, tl=0;
for(int i=1;i<=n;i++){
while(hd <= tl && qu3[tl] <= mx[i][j]) -- tl;
qu3[++ tl] = mx[i][j];
qu4[tl] = i;
if(i < k)continue;
while(hd <= tl && i - qu4[hd] >= k) ++ hd;
mx2[i - k + 1][j] = qu3[hd];
}
}
}
};
namespace s2{
int qu[maxn],qu2[maxn],qu3[maxn],qu4[maxn];
void solve(){
for(int i=1;i<=n;i++){
int hd=1, tl=0;
for(int j=1;j<=m;j++){
while(hd <= tl && qu[tl] >= a[i][j])-- tl;
qu[++ tl] = a[i][j];
qu2[tl] = j;
if(j < k)continue;
while(hd <= tl && j - qu2[hd] >= k) ++ hd;
mn[i][j - k + 1] = qu[hd];
}
}
for(int j=1;j<=m-k+1;j++){
int hd=1, tl=0;
for(int i=1;i<=n;i++){
while(hd <= tl && qu3[tl] >= mn[i][j]) -- tl;
qu3[++ tl] = mn[i][j];
qu4[tl] = i;
if(i < k)continue;
while(hd <= tl && i - qu4[hd] >= k) ++ hd;
mn2[i - k + 1][j] = qu3[hd];
}
}
}
};
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)scanf("%lld",&a[i][j]);
s1::solve();
s2::solve();
LL ans = 1e18;
for(int i=1;i<=n-k+1;i++)
for(int j=1;j<=m-k+1;j++)
ans = min(ans, mx2[i][j] - mn2[i][j]);
printf("%lld\n",ans);
return 0;
}
- 单调队列优化dp
常见形式是:\(dp[i] = max{dp[j]} + w\),其中j在某个与i有关的范围内
用单调队列维护dp数组
Luogu2034
\(j \in [i-k-1, i-1]\)
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+ 5;
int n,k;
int a[maxn];
LL dp[maxn], qu[maxn], qu2[maxn];
signed main(){
scanf("%d%d",&n,&k);
LL sum = 0;
for(int i=1;i<=n;i++)scanf("%d",&a[i]), sum += a[i];
int hd=1, tl=0;
// for(int i=1;i<=k;i++)dp[i] = 0, qu[++ tl] = 0, qu2[tl] = i;
qu[++ tl] = 0;qu2[tl] = 0;
for(int i=1;i<=n;i++){
while(hd <= tl && qu[tl] >= qu[hd] + a[i])-- tl;
dp[i] = qu[hd] + a[i]; // i-k-1 .. i-1
qu[++ tl] = dp[i];
qu2[tl] = i;
while(hd <= tl && i - qu2[hd] >= k+1)++ hd;
}
// printf("%lld\n",dp[3]);
LL r = 1e18;
for(int i=n-k;i<=n;i++)r = min(r, dp[i]);
printf("%lld\n",sum - r);
return 0;
}
Luogu3957
形式相同,只不过\(j \in [i-mx, i-mn]\)
可以用一个\(O(n log^2n)\)+剪枝的暴力AC
考虑单调队列做法,其实差不多,只不过入队和出队的时候需要判断一下,注意顺序matters!
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>
using namespace std;
typedef long long LL;
//#define int LL
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 5e5 + 5;
int n,d,k;
int x[maxn], s[maxn];
LL dp[maxn];
LL qu[maxn], qu3[maxn];
int qu2[maxn], qu4[maxn];
int check(int lim){
int mn = max(d-lim, 1), mx = d + lim;
int hd=1, tl=0;
for(int i=1;i<=n;i++)qu[i] = qu2[i] = 0;
int l=0, r=0;
for(int i=1;i<=n;i++){
while(x[i] - x[r] >= mn){
while(hd <= tl && qu[tl] <= dp[r])-- tl;
qu[++ tl] = dp[r];qu2[tl] = r;
++ r;
}
while(x[i] - x[l] > mx){ // 放在后面以排除 距离 > mx 且 > mn的不合法情况
if(qu[hd] == dp[l] && qu2[hd] == l)++ hd;
++ l;
}
if(hd > tl)dp[i] = -1e18;
else dp[i] = qu[hd] + s[i];
if(dp[i] >= k)return 1;
// printf("%d %d %d %d\n",i,dp[i], hd,tl);
}
return 0;
}
signed main(){
// freopen("3957_6.in","r",stdin);
scanf("%d%d%d",&n,&d,&k);
LL r1 = 0;
for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&s[i]), r1 += s[i] >= 0 ? s[i] : 0;
if(r1 < k){
puts("-1");
return 0;
}
// printf("%d\n",check(235));return 0;
int l=1, r=1e9, ans;
while(l <= r){
int mid = l+r>>1;
if(check(mid))r = mid-1, ans = mid;
else l = mid+1;
}
printf("%d\n",ans);
return 0;
}