花盆

题目传送门

单调队列的题目还是滑动窗口最典型,如果猜到可以用单调队列解题,看看能否通过什么方法转化成滑动窗口(如二分答案)。单调队列的题一般涉及最大值和最小值的问题,好像有的题目也可以用ST表解决。
这道题因为知道是单调队列才做的(硬上单调队列...)
简化题意:找到\(x\)坐标相差最近的两个点,使它们的纵坐标差值大于等于\(D\)
(先将点按照\(x\)由小到大排序)
最开始两个思路:

思路一:枚举每个点,把它当做低的那个点(这样答案一定会被枚举到)。这就要找它左边比它高的点(右边的情况倒着再来一遍)。维护一个单调递减的序列,因为\(x\)使有序的,所以单调队列中越靠左的点里当前点越远,并且越高。因此二分出(或者直接枚举)一个符合条件的\(y\),用它和当前点的距离更新答案。
思路二:二分一个答案\(L\),还是枚举每个点,把它当做低的那个点(这样答案一定会被枚举到)。这就要找它左边比它高的点(右边的情况倒着再来一遍)。维护一个单调递减的序列,因为\(x\)使有序的,所以单调队列中越靠左的点里当前点越远,并且越高。所以二分出\(x\)据当前点小于等于\(L\)的单调队列中最左边的点,只要它们两个的纵坐标的差值大于等于\(D\),就\(return\) \(true\)。如果一直没有\(return\),那么就不可行。

只写了思路二的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define mid ((l+r)>>1)
#define midd ((ll+rr)>>1)
using namespace std;
const LL N = 100005;
LL n,D,maxx,minn=1000006,l=1,r;
LL maxxl,minnl=1000006,ans;
struct node{
    LL x,y;
    bool friend operator < (const node& a,const node& b)
    { return a.x<b.x; }
}a[N],q[N];
LL aaa[40000000];
inline int read()
{
    int ans=0,w=1;
    char c=getchar();
    while((c<'0'||c>'9')&&c!='-') c=getchar();
    if(c=='-') { w=-1; c=getchar(); }
    while(c>='0'&&c<='9')
    { ans=ans*10+c-'0'; c=getchar(); }
    return ans*w;
}
bool check(LL u)
{
    l=1,r=0;
    for(LL i=1;i<=n;i++)
    {
        while(q[r].y<=a[i].y&&r) --r;
        LL ll=1,rr=r,ansp=-1;
        while(ll<=rr)
        {
            if(a[i].x-q[midd].x<=u) ansp=midd,rr=midd-1;
            else ll=midd+1;
        }
        if(q[ansp].y-a[i].y>=D&&ansp>0) return true;
        q[++r].x=a[i].x;
        q[r].y=a[i].y;
    }
    l=1;r=0;
    for(LL i=n;i>=1;i--)
    {
        while(q[r].y<=a[i].y&&r) --r;
        LL ll=1,rr=r,ansp=-1;
        while(ll<=rr)
        {
            if(q[midd].x-a[i].x<=u) ansp=midd,ll=midd+1;
            else rr=midd-1;
        }
        if(q[ansp].y-a[i].y>=D&&ansp>0) return true;
        q[++r].x=a[i].x;
        q[r].y=a[i].y;
    }
    return false;
}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
//  cin>>n>>D;
//  scanf("%lld%lld",&n,&D);
    n=read(); D=read();
    for(LL i=1;i<=n;i++)
    {
        scanf("%lld%lld",&a[i].x,&a[i].y);
        minn=min(minn,a[i].y);
        maxx=max(maxx,a[i].y); 
        minnl=min(minnl,a[i].x);
        maxxl=max(maxxl,a[i].x); 
    }
//  cout<<maxx<<"*"<<minn<<endl;
    if(maxx-minn<D) { printf("-1\n"); return 0; }
    sort(a+1,a+n+1);
    LL l=1,r=maxxl-minnl;
    ans=r;
    while(l<=r)
    {
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

写完另一个算法,又回来改了半天,还是没改对,虽然感觉是和另一个算法一样的错误。

题解思路:二分出一个\(L\),求从左到右每一个长度为\(L\)的区间中的最大值和最小值(滑动窗口),看是否有满足条件的。

第一次交的时候90分:
enter image description here
100分:
enter image description here

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1) 
using namespace std;
const int N = 100005;
const int inf = 2147483647;
int n,D,l1=1,r1,l2=1,r2;
int ans,min1=inf,min2=inf,max1,max2;
struct node{
    int x,y;
    // bool friend operator <(const node& a,const node& b)
    // { return a.x<b.x; }
}a[N],q1[N],q2[N];
bool cmp(node a,node b)
{ return a.x<b.x; }
bool check(int u)
{
    l1=l2=1; r1=r2=0; 
    for(int i=1;i<=n;i++)
    {
        while(l1<=r1&&a[i].x-q1[l1].x>u) ++l1;
        while(l2<=r2&&a[i].x-q2[l2].x>u) ++l2;
        while(q1[r1].y<=a[i].y&&r1) --r1;
        while(q2[r2].y>=a[i].y&&r2) --r2;
        q1[++r1].x=a[i].x; q1[r1].y=a[i].y;
        q2[++r2].x=a[i].x; q2[r2].y=a[i].y;
        if(q1[l1].y-q2[l2].y>=D) return true;
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&D);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i].x,&a[i].y);
        min1=min(min1,a[i].x);
        min2=min(min2,a[i].y);
        max1=max(max1,a[i].x);
        max2=max(max2,a[i].y);
    }
    if(max1-min1<D) { printf("-1\n"); return 0 ;}
    sort(a+1,a+n+1,cmp);
    int l=1,r=max1-min1;
    while(l<=r)
    {
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/karryW/p/11374030.html