最大值维护一个下降序列时,才能保证队头是最大值。
同理,最小值维护一个上升序列时,才能保证队头是最小值。
过渡题
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。
比如8个数的数列[1 3 -1 -3 5 3 6 7],m=3,那么每连续3个最大值如下:
位置
最大值
[1 3 -1] -3 5 3 6 7
3
1 [3 -1 -3] 5 3 6 7
3
1 3 [-1 -3 5] 3 6 7
5
1 3 -1 [-3 5 3] 6 7
5
1 3 -1 -3 [5 3 6] 7
6
1 3 -1 -3 5 [3 6 7]
7
【输入格式】
第一行两个整数n和m( 1<= n <= 20 0000,m<=n)。
下来给出n个整数。
【输出格式】
一行一个整数,表示每连续m个数的最大值。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
inline void qw(ll x)
{
if(x<0)x=-x,putchar('-');
if(x/10)qw(x/10);
putchar(x%10+48);
}
struct node
{
int p;ll w;
node(){}
node(int p,ll w):p(p),w(w){}
}list[N];
int head,tail;
ll a[N],f[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)qr(a[i]);list[1]=node(1,a[1]);head=1,tail=1;
for(int i=2;i<=n;i++)
{
while(head<=tail&&i-list[head].p>=m)++head;//踢队头(太远了)
while(head<=tail&&list[tail].w<a[i])--tail;//踢队尾(太小了)
list[++tail]=node(i,a[i]);f[i]=list[head].w;
}
for(int i=m;i<=n;i++)qw(f[i]),puts("");
return 0;
}
模板题
【题意】
从N个数(N<=100000)中选若干个数,要求在连续M(M<=N)个数里至少要有一个数被选择。 求选出来数的最小总和。
【输入格式】
第一行两个整数 N,M;
接下来N个数ai(ai<=100)表示第i个数。
【输出格式】
一个整数,最小总和。
【样例输入】
5 3
1
2
5
6
2
【样例输出】
4
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
inline void qw(ll x)
{
if(x<0)x=-x,putchar('-');
if(x/10)qw(x/10);
putchar(x%10+48);
}
struct node
{
int p;ll w;
node(){}
node(int p,ll w):p(p),w(w){}
}list[N];
int head,tail;
ll a[N],f[N];//f数组记录和
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)qr(a[i]);list[1]=node(0,0);head=1,tail=1;
for(int i=1;i<=n;i++)
{
//head保持最小,且有效。
while(head<=tail&&i-list[head].p>m)++head;//连续m个数至少要选一个,那么两个数之间不能超过m个
f[i]=list[head].w+a[i];//最小的head+a[i]
while(head<=tail&&list[tail].w>f[i])--tail;//如果队尾的数大于当前,那么这个状态就没有意义
list[++tail]=node(i,f[i]);
}
ll ans=(1<<30);
for(int i=n-m+1;i<=n;i++)ans=min(ans,f[i]);qw(ans);puts("");
return 0;
}
【题意】
一个数列有N(1 <= N <= 100,000)个数(0<=ai<=10^9),要求从中选若干个,使得选出来的数之和最大。不能选中超过连续M(M<=N)个。
【输入格式】
第一行两个整数N和M;
下来给出n个数。
【输出格式】
一行一个整数,表示最大的和。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
inline void qw(ll x)
{
if(x<0)x=-x,putchar('-');
if(x/10)qw(x/10);
putchar(x%10+48);
}
int head,tail;
int q[N];
ll a[N],f[N][2],s[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)qr(a[i]),s[i]=s[i-1]+a[i];
q[1]=0;head=1,tail=1;
for(int i=1;i<=n;i++)
{
f[i][0]=max(f[i-1][1],f[i-1][0]);
while(head<=tail&&i-q[head]>m)++head;
f[i][1]=f[q[head]][0]+(s[i]-s[q[head]]);
while(head<=tail&&s[i]-f[i][0]<s[q[tail]]-f[q[tail]][0])--tail;//意味着我选i比选tail时损失更小了
q[++tail]=i;
}
qw(max(f[n][1],f[n][0])),puts("");
return 0;
}
【题意】
一个猴子要吃香蕉,一共N棵香蕉树排列在一条直线上,它一开始在第一棵树上。
每棵树上有不同数量的香蕉,猴子每次最多的跳跃距离为D,而且最多只能跳M次,问猴子最多能吃到多少香蕉?
【输入格式】
第一行 三个整数 N,D,M (M<N<=5000,D<=10000);
下面N行 每行两个整数 ai,bi (ai,bi<=1000000) 分别表示每棵树上的香蕉数目,以及每棵树的位置(树的位置是递增的)。
数据保证没有两棵香蕉树在同一位置,以及b[1]=0。
【输出格式】
一个整数,表示猴子最多吃到的香蕉数。
【样例输入】
5 5 2
6 0
8 3
4 5
6 7
9 10
【样例输出】
20
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define gc getchar()
using namespace std;
const int N=5010;
struct node{int p,w;}a[N];
int last[N],now[N],q[N];
inline void qr(int &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
void qw(int x)
{
if(x<0)x=-x,putchar('-');
if(x/10)qw(x/10);
putchar(x%10+48);
}
int main()
{
int n,d,m;qr(n),qr(d),qr(m);
for(int i=1;i<=n;i++)
{
qr(a[i].w),qr(a[i].p);
if(a[i].p-a[i-1].p>d){m=min(i-1,m);break;}
}
int ans=now[1]=a[1].w,head,tail;
for(int j=1;j<=m;j++)
{
memcpy(last,now,n<<2);
q[1]=j;head=tail=1;
for(int i=j+1;i<=n;i++)
{
while(head<=tail&&a[i].p-a[q[head]].p>d)++head;
if(head>tail)break;now[i]=last[q[head]]+a[i].w;
if(last[i])
{
while(head<=tail&&last[q[tail]]<last[i])--tail;
q[++tail]=i;
}
ans=max(ans,now[i]);
}
}
qw(ans),puts("");
return 0;
}
【题意】
给一个N∗*∗M的数矩阵。求一个子矩阵,要求子矩阵中最大值与最小值的差<=C,而且子矩阵的宽(横)不超过100,长(竖)没有限制。
求子矩阵的最大面积。
【输入格式】
第一行两个整数 M(左右方向),N(上下方向)和 C (N,M<=500 0<=C<= 10 );
接下来 N行 每行M个整数(每个数的范围为-30000至30000)。
【输出格式】
子矩阵的最大面积(长*宽)。
【样例输入】
10 15 4
41 40 41 38 39 39 40 42 40 40
39 40 43 40 36 37 35 39 42 42
44 41 39 40 38 40 41 38 35 37
38 38 33 39 36 37 32 36 38 40
39 40 39 39 39 40 40 41 43 41
39 40 41 38 39 38 39 39 39 42
36 39 39 39 39 40 39 41 40 41
31 37 36 41 41 40 39 41 40 40
40 40 40 42 41 40 39 39 39 39
42 40 44 40 38 40 39 39 37 41
41 41 40 39 39 40 41 40 39 40
47 45 49 43 43 41 41 40 39 42
42 41 41 39 40 39 42 40 42 42
41 44 49 43 46 41 42 41 42 42
45 40 42 42 46 42 44 40 42 41
【样例输出】
35
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define gc getchar()
using namespace std;
const int N=710;
inline void qr(int &x)
{
x=0;int f=1;char c=gc;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
x*=f;
}
void qw(int x)
{
if(x<0)x=-x,putchar('-');
if(x/10)qw(x/10);
putchar(x%10+48);
}
int a[N][N],wmax[N],wmin[N],l1,l2,r1,r2,q1[N],q2[N];
inline void ins1(int x)
{
while(l1<=r1&&wmax[q1[r1]]<wmax[x])--r1;//维护一个下降序列
q1[++r1]=x;
}
inline void ins2(int x)
{
while(l2<=r2&&wmin[q2[r2]]>wmin[x])--r2;//维护一个上升序列
q2[++r2]=x;
}
int main()
{
int n,m,c;qr(m),qr(n),qr(c);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
qr(a[i][j]);
int ans=0;
for(int j=1;j<=m;j++)//左边界
{
for(int i=1;i<=n;i++)wmax[i]=wmin[i]=a[i][j];
int u=min(j+99,m);
for(int i=j+1;i<=u;i++)//右边界
{
for(int k=1;k<=n;k++)
wmax[k]=max(wmax[k],a[k][i]),
wmin[k]=min(wmin[k],a[k][i]);
l1=l2=1;r1=r2=0;
int head=1,tail=1,w=i-j+1;
while(tail<=n&&(n-head+1)*w>ans)
{
ins1(tail),ins2(tail);
while(head<=tail&&l1<=r1&&l2<=r2&&wmax[q1[l1]]-wmin[q2[l2]]>c)
{
++head;
while(l1<=r1&&q1[l1]<head)++l1;
while(l2<=r2&&q2[l2]<head)++l2;
}
ans=max(ans,(tail-head+1)*w);
++tail;
}
}
}
qw(ans),puts("");
return 0;
}
简单说一下最后一道题吧。
根据这道题,由于它的宽(横)有限制,而长(竖)无限制,那么我们便可以仅枚举左边界和右边界,就可以制造出一个又一个的子矩阵,通过单调队列来维护当前子矩阵的最大值与最小值,即可求解。