King of Range
题意:
给你n个数,有m个询问,每次询问一个x,问有多少个区间的最大值减最小值大于x
题解:
我一开始的想法
st表实现区间最大减最小,利用二分来找这个区间范围,复杂度O(nmlogn),正好被卡
一个签到题给做复杂了
我们这样想,我们定义左端i,然后去移动右端点pos,当出现pos大于k时,此时pos位置之后的肯定与l组成的区间也大于k,然后左端点移动(i++),右端点在pos继续走(pos不清零),为什么?
结合代码分析:
我们每次记录答案都是当区间差值刚好大于k时退出,也就是pos位置肯定是最大值或者最小值(会影响差值,不然也不可能跳出while循环)。记录完答案后左端点移动一位,此时区间是[i+1,pos],第l位没了,但是如果此时区间差值还是大于k,说明区间最值不是第i位,pos之后的右端点都是可以的,直接累加答案,如果小于等于k,继续右推右端点。
相当于这个差值区间是有这种性质,可以直接在前一个的基础上继承过来,不用重新找
这样复杂度应该是(2n)
总复杂度是(2mn)
for(int i = 1;i <= n;i++){while(query_max(i,pos)-query_min(i,pos)<=k&&pos+1<=n){pos++;}if(query_max(i,pos)-query_min(i,pos)>k){ans+=n-pos+1;}
}
官方题解是这样做的:
因为区间端点都是单调的,所以维护两个单调队列,其中一个递增序列,队首维护最小值,一个递减序列,队首维护最大值,每次弹出两个队列中队首考前的一个,直到极差<=k,这样就可以在均摊O(1)的时间内求Ri了
其实就相当于维护区间最大最小值,更新左端点,在左端点右侧的就要被弹出,这样一直维护
O(nm)
题解:
st+尺取
#include<bits/stdc++.h>using namespace std;
const int inf=0x3f3f3f3f;
typedef long long ll;
const int maxn = 1e5+50;int ffmax[maxn][20];
int ffmin[maxn][20];
int a[maxn];
int n,m,k;
double lg2[maxn];
int h;
int max2(int x,int y)
{if(x<y)return y;return x;
}
int min2(int x,int y)
{if(x<y)return x;return y;
}
void init(){memset(ffmax,0,sizeof(ffmax));memset(ffmin,0x3f,sizeof(ffmin));for(int i = 1;i <= n;i++){ffmax[i][0] = ffmin[i][0] = a[i];}for(int j = 1;j <=int(lg2[n]/lg2[2]);j++){ for(int i = 1;i+(1<<j)-1<= n;i++){ ffmax[i][j] = max2(ffmax[i][j-1],ffmax[i+(1<<(j-1))][j-1]);ffmin[i][j] = min2(ffmin[i][j-1],ffmin[i+(1<<(j-1))][j-1]);}}} int query_max(int l,int r){int s = int(lg2[r-l+1]/lg2[2]);//cout<<l<<" "<<l+(1<<s)-1<<" "<<r<<" "<<r-(1<<s)+1<<" "<<s<<endl;return max2(ffmax[l][s],ffmax[r-(1<<s)+1][s]);
}int query_min(int l,int r){int s = int(lg2[r-l+1]/lg2[2]);return min2(ffmin[l][s],ffmin[r-(1<<s)+1][s]);
}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=100000;i++)lg2[i]=log(i); //cout<<lg2[2];h=int(lg2[n]/lg2[2]);for(int i = 1;i <= n;i++) scanf("%d",&a[i]);init();//cout<<ffmax[3][2]<<endl;while(m--){scanf("%d",&k);ll ans = 0;int pos=0;for(int i = 1;i <= n;i++){while(query_max(i,pos)-query_min(i,pos)<=k&&pos+1<=n){pos++;}if(query_max(i,pos)-query_min(i,pos)>k){ans+=n-pos+1;}}printf("%lld\n",ans);} return 0;
}
/*
10 10
1 2 3 4 5 6 7 8 9 10
2
1 2 3 4 5*/
单调队列做法:
#include<bits/stdc++.h>
using namespace std;
long long a[100100];
long long qmax[100100];
long long qmin[100100];
int main(){int n,m,k;scanf("%d %d",&n,&m);for(int i=1;i<=n;++i){scanf("%d",&a[i]);}while(m--){scanf("%d",&k); long long ans=0;int l=1,h1=1,t1=0,h2=1,t2=0;for(int i=1;i<=n;i++){while(h1<=t1&&a[i]>=a[qmax[t1]]) t1--; while(h2<=t2&&a[i]<=a[qmin[t2]]) t2--;qmax[++t1]=i;qmin[++t2]=i;while(h1<=t1&&h2<=t2&&a[qmax[h1]]-a[qmin[h2]]>k){ans+=n-i+1;l++;if(qmax[h1]<l) h1++;if(qmin[h2]<l) h2++;}} cout<<ans<<endl;}
}