Q:
给定一个长度为N的序列(可能有负数),
从中找出一段长度不超过M的连续子序列,使得其和最大
N ≤ 500000 N≤500000 N≤500000
A:
对于这题
首先不难想到先求出数列前缀和sum[]
那么显然问题的答案就是 m a x i = m n ( s u m [ i ] − m i n j = i − m i − 1 ( s u m [ j ] ) ) max_{i=m}^n(\ sum[i]-min_{j=i-m}^{i-1}(sum[j])\ ) maxi=mn( sum[i]−minj=i−mi−1(sum[j]) )
通俗的说
我们需要维护整个数列中所有长度为m的连续子序列的最小值
线段树??ST表???
虽然都是nlogn的算法,但对于这题显然还是略显吃力
所以这时考虑引入我们的单调队列
单调队列需要一个双端队列实现
可以直接维护每个长度为m的连续子数列的最小值
具体怎么做呢
我们从第一个元素一次往后遍历整个序列,每遍历一个元素
1.检查当前队列中队首元素位置距当前元素是否超过m,是则不断从队首弹出直到小于m个
2.检查队尾元素是否大于等于当前元素,是则不断从队尾弹出元素,
直到队列为空或队尾元素小于当前元素
3.将当前元素入队
此时队首保存的就是从当前元素开始往前m个元素中的最小值了
整个算法复杂度只有 O ( N ) O(N) O(N)
当然,我们在实际操作时
队列中可以只保存编号,进行2操作时只要映射到原序列对应元素
这样就可以省去用结构体同时储存值与编号的麻烦
就是上面的问题
#include
#include
#include
#include
#include
#include
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=500010;
int n,m;
int sum[maxn];
int q[maxn],ll=1,rr=1;
int ans=-1e9;
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
sum[i]=read(),sum[i]+=sum[i-1];
for(int i=1;i<=n;i++)
{
while(ll<rr&&q[ll]<i-m) ll++;//检查队列中元素个数
ans=max(ans,sum[i]-sum[q[ll]]);
//此题中查询最小值不包含当前元素,所以入队前先更新
while(ll<rr&&sum[i]<=sum[q[rr-1]]) rr--;//检查队尾元素与当前元素大小
q[rr++]=i;//当前元素入队
}
printf("%d",ans);
return 0;
}
有n+1个格子,起点在格子0,每个格子有一个数值,每次能向编号大的格子跳,跳跃距离为[L,R],求能得到的最大分值总和
d p [ i ] dp[i] dp[i]表示跳到 i i i能获得的最大分数, d p [ i ] = d p [ j ] + A [ i ] ( i − r ≤ j ≤ i − l ) dp[i]=dp[j]+A[i](i-r\leq j\leq i-l) dp[i]=dp[j]+A[i](i−r≤j≤i−l)
单调队列维护的区间与当前位置不连续
ll=rr=1; dp[0]=a[0];
for(int i=L;i<=n;++i)
{
while(ll<rr&&dp[q[rr-1]]<=dp[i-L]) rr--;
q[rr++]=i-L;
while(ll<rr&&q[ll]<i-R) ll++;
dp[i]=dp[q[ll]]+a[i];
if(i>=n-R) ans=max(ans,dp[i]);
}
在上述问题的基础上,若给定的格子位置不连续(pos[i]为第i个格子的位置)
显然每次能转移到pos[i]的合法区间会随着i的增加而增加(即转移区间有单调性)
可以用一个指针p维护距离当前位置大于等于L的最近的位置,即先让队内元素满足 j ≤ i − l j\leq i-l j≤i−l
再不断弹出队首不满足 i − r ≤ j i-r\leq j i−r≤j的位置
语死早,不太会描述,看看代码意会一下吧
int p=0; ll=rr=1;
memset(dp,128,sizeof(dp)); dp[0]=a[0];
for(int i=1;i<=n;++i)
{
while(pos[i]-pos[p]>=L&&p<i)
{while(dp[q[rr-1]]<=dp[p]&&ll<rr)--rr; q[rr++]=p++;}
while(pos[i]-pos[q[ll]]>R&&ll<rr)++ll;
if(ll>=rr||dp[q[ll]]==-inf) continue; //没有合法的位置可以转移
dp[i]=dp[q[ll]]+a[i];
if(i>=n-R) ans=max(ans,dp[i]);
}
给定n,k和一个长度为n的序列,求最长的最大值最小值相差不超过k的序列
二分序列长度mid,单调队列找每个长度为mid的子序列的最小/大值即可
#include
#include
#include
#include
#include
#include
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=3000010;
int n,k;
int a[maxn];
int q[2][maxn],ll[2],rr[2];
int ans;
int check(int m)
{
ll[0]=ll[1]=rr[0]=rr[1]=1;
q[0][1]=q[1][1]=0;
for(int i=1;i<=n;++i)
{
while(ll[0]<rr[0]&&q[0][ll[0]]<i-m+1)++ll[0];
while(ll[0]<rr[0]&&a[q[0][rr[0]-1]]>=a[i])--rr[0];
while(ll[1]<rr[1]&&q[1][ll[1]]<i-m+1)++ll[1];
while(ll[1]<rr[1]&&a[q[1][rr[1]-1]]<=a[i])--rr[1];
q[0][rr[0]++]=i; q[1][rr[1]++]=i;
if(i>=m&&a[q[1][ll[1]]]-a[q[0][ll[0]]]<=k)return 1;
}
return 0;
}
int main()
{
k=read();n=read();
for(int i=1;i<=n;++i)a[i]=read();
int L=1,R=n,mid;
while(L<R)
{
mid=L+R>>1;
if(check(mid))ans=mid,L=mid+1;
else R=mid;
}
if(check(L))ans=L;
printf("%d",ans);
return 0;
}
一样二分花盆宽度,单调队列检查该长度区间内的y坐标最大/小值差值
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long lt;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
int n,D,mx;
struct node{int x,y;}p[maxn];
int q[2][maxn],ll[2],rr[2];
bool cmp(node a,node b){ return a.x<b.x;}
int check(int w)
{
ll[0]=ll[1]=rr[0]=rr[1]=1;
for(int i=1;i<=n;++i)
{
while(ll[0]<rr[0]&&p[i].x-p[q[0][ll[0]]].x>w) ++ll[0];
while(ll[1]<rr[1]&&p[i].x-p[q[1][ll[1]]].x>w) ++ll[1];
while(ll[0]<rr[0]&&p[i].y<=p[q[0][rr[0]-1]].y) --rr[0];
while(ll[1]<rr[1]&&p[i].y>=p[q[1][rr[1]-1]].y) --rr[1];
q[0][rr[0]++]=i; q[1][rr[1]++]=i;
if(p[q[1][ll[1]]].y-p[q[0][ll[0]]].y>=D) return 1;
}
return 0;
}
int main()
{
n=read();D=read();
for(int i=1;i<=n;++i)
p[i].x=read(),p[i].y=read(),mx=max(mx,p[i].x);
sort(p+1,p+1+n,cmp);
int L=0,R=mx,ans=0;
while(L<R)
{
int mid=L+R>>1;
if(check(mid)) R=mid,ans=mid;
else L=mid+1;
}
if(ans==0) printf("-1");
else printf("%d",ans);
return 0;
}