luoguP2698 [USACO12MAR]Flowerpot S(单调队列,ST表)
题目链接
这道题要求我们求一段最小的区间长度,使得区间最大值减区间最小值大于等于 \(D\) 。
\(\operatorname{Solution.1}\)
看到区间最大值最小值,可以很快的想到 ST表 。优秀的 ST表 资瓷 \(\operatorname{O(logn)}\) 预处理,\(\operatorname{O(1)}\) 查询区间最大值和最小值。于是解法就出来了。预处理每一段区间的最大值最小值,二分花盆的长度,检验的时候从 \(1\to n\) 枚举,\(\operatorname{O(1)}\) 查询 \([1,1+x]\) ,\([2,2+x]\) ,\(...\) ,\([n-x,n]\) 的区间最大值最小值之差,只要有一个满足大于等于 \(D\) 这种花盆的长度就是合法的。
总时间复杂度 \(\operatorname{O(nlogn)}\) 。
\(\operatorname{Solution.2}\)
看到一个固定长度的区间最大值最小值,很快可以想到滑动窗口。依然是二分枚举花盆的长度,检验时用滑动窗口检验维护区间最大值和最小值并计算是否有合法的。
一次滑动窗口的复杂度为 \(\operatorname{O(n)}\) (每个元素只会入队一次),二分复杂度 \(\operatorname{O(logn)}\) ,总复杂度 \(\operatorname{O(nlogn)}\)
\(\operatorname{Solution.3}\)
依然是考虑滑动窗口。 先按 \(x\) 排序,然后用两个滑动窗口分别维护区间最大值最小值,但此时不用二分了。枚举每次所求区间的开头(\(l\in [1,n]\),\(l=1\to n\) ),排除队列中不合法的元素(下标小于 \(l\) ),当最大值最小值之差小于 \(D\) 的时候不断往后扩展右边界 \(r\) ,每扩展一个右边界就弹出队列中不合法的元素(维护队列单调性),最后更新答案。
由于每一个位置只可能进队一次(作为 \(r\) 的时候),时间复杂度 \(\operatorname{O(n)}\) 。
\(\operatorname{Code}\)
\(\operatorname{Solution.1/2/3}\) 分别对应 \(\operatorname{Solve1/2/3}\) 函数。
这里其实在写第二种时时发现自己怎么也调不对后来发现二分跳出循环的条件写成了 l<r
。
#include <bits/stdc++.h>
#define ll long long
#define maxn 1000005
using namespace std;
ll n,d;
ll maxi;
ll ans=1e18;
ll u,v;
ll dmin[maxn][21];
ll dmax[maxn][22];
struct nott { ll x,y; }a[maxn];
inline ll read(){
ll x=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return f?-x:x;
}
inline ll max(ll a,ll b) { return a>b?a:b; }
inline ll min(ll a,ll b) { return a<b?a:b; }
inline ll query(ll l,ll r){
ll lg=log2(r-l+1);
ll maxni=max(dmax[l][lg],dmax[r-(1<<lg)+1][lg]);
ll minni=min(dmin[l][lg],dmin[r-(1<<lg)+1][lg]);
return maxni-minni;
}
inline int check(ll x){
for(register int i=1;i<=maxi-x;++i){
ll to=i+x;
if(query(i,to)>=d)
return 1;
}
return 0;
}
inline void solve1_ST(){
memset(dmin,0x3f,sizeof(dmin));
for(register int i=1;i<=n;++i){
u=read();v=read();
maxi=max(maxi,u);
dmax[u][0]=max(dmax[u][0],v);
dmin[u][0]=min(dmin[u][0],v);
}
ll lg=log2(maxi);
for(register int j=1;j<=lg;++j){
for(register int i=1;i<=maxi-(1<<j)+1;++i){
dmax[i][j]=max(dmax[i][j-1],dmax[i+(1<<j-1)][j-1]);
dmin[i][j]=min(dmin[i][j-1],dmin[i+(1<<j-1)][j-1]);
}
}
ll l=1,r=maxi;
ans=-1;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid))
ans=mid,r=mid-1;
else
l=mid+1;
}
printf("%lld\n",ans);
}
inline int cmp1(nott aa,nott bb) { return aa.x<bb.x; }
ll q1[maxn],q2[maxn],hd1,hd2,tl1,tl2;
ll s1[maxn],s2[maxn];
inline int check1(ll x){
hd1=hd2=1,tl1=tl2=0;
for(register int i=1;i<=n;++i){
while(hd1<=tl1&&q1[tl1]<=a[i].y) --tl1;q1[++tl1]=a[i].y;
while(hd2<=tl2&&q2[tl2]>=a[i].y) --tl2;q2[++tl2]=a[i].y;
s1[tl1]=a[i].x,s2[tl2]=a[i].x;
while(s1[hd1]<=a[i].x-x-1&&hd1<=tl1) ++hd1;
while(s2[hd2]<=a[i].x-x-1&&hd2<=tl2) ++hd2;
if(q1[hd1]-q2[hd2]>=d)
return 1;
}
return 0;
}
inline void solve2_queue(){
for(register int i=1;i<=n;++i) a[i].x=read(),a[i].y=read(),maxi=max(maxi,a[i].x);
sort(a+1,a+n+1,cmp1);
ll l=1,r=maxi;
while(l<=r){
ll mid=(l+r)>>1;
if(check1(mid))
r=mid-1,ans=mid;
else
l=mid+1;
}
printf("%lld\n",ans>=1e18?-1:ans);
}
inline int cmp(nott aa,nott bb) { return aa.x<bb.x; }
inline void solve3_queue(){
for(register int i=1;i<=n;++i) a[i].x=read(),a[i].y=read();
sort(a+1,a+n+1,cmp);
hd1=hd2=1;
for(register int l=1,r=0;l<=n;++l){
while(hd1<=tl1&&q1[hd1]<l) ++hd1;
while(hd2<=tl2&&q2[hd2]<l) ++hd2;
while(a[q1[hd1]].y-a[q2[hd2]].y<d&&r<n){
++r;
while(a[q1[tl1]].y<a[r].y&&hd1<=tl1) --tl1;q1[++tl1]=r;
while(a[q2[tl2]].y>a[r].y&&hd2<=tl2) --tl2;q2[++tl2]=r;
}
if(a[q1[hd1]].y-a[q2[hd2]].y>=d)
ans=min(ans,a[r].x-a[l].x);
}
printf("%lld\n",ans>=1e9?-1:ans);
}
int main(){
n=read();d=read();
// solve1_ST();
solve2_queue();
// solve3_queue();
return 0;
}
\(\operatorname{Thanks}\)
神仙们的题解。