[CF765F]Souvenirs

57 篇文章 0 订阅
15 篇文章 0 订阅

题目大意

给定一个序列
多次询问一个区间最接近两个数的差值
最接近的两个数可以相同,但不能是同一个位置上的数。
允许离线

强大线段树做法

我们首先可以扫描线
从左到右扫,每次处理右端点在扫描线上的所有询问。
我们维护一颗线段树,线段树每个节点的值没有太多实际的意义,但它需要满足一个性质:
假如当前扫描线在now,那么在线段树中查询[l,now]这个区间的答案就是正确的答案
听起来很不可思议?
这样每次询问直接询问即可
我们思考每次从now-1到now,线段树哪些信息需要更改,此时我们加入了一个a[now],设为d。
我们可以先去定位区间[1,now],规定优先走右儿子。
对于一个被定位的区间[L,R]满足R<=now,什么情况下不需要在对它包括它子树的信息进行更改了呢?
因为我们优先走右儿子,那么我们可以维护出一个mi表示此时[R+1,now]的答案。
假如对于[L,R],d在[L,R]中找到的每一个数t(注意如果R=now,t并不能去a[now]),都有 |dt|>=mi ,那么这个点上的值不更改也可以满足我们线段树需要满足的条件,即询问[i,now]得到正确的答案。于是我们更新mi后直接退出。
否则的话,我们可能要更改这个区间上的值,我们暴力递归左右,这里也是优先走右边。
为了验证是否还需要往下做,每个区间维护一个set,每次给d找前驱和后继即可。
因为不能包含a[now],所以每次做完修改再把now所处的区间的set里都塞一个a[now]。
这样做正确性是没有问题的,时间复杂度如何呢?
我们只需要考虑一直递归到叶子的位置,其他的都是区间,区间定位的复杂度会和递归到叶子保持一致(假设相邻两个递归到叶子的位置i和j,那么在到i叶子路径和到j叶子路径第一次分开后,i那条每往左走右边那个区间被定位为[i+1,j-1]里的直接退出,j那条同理,可以看出两者复杂度同阶)。
一个位置i什么时候会被递归到叶子,显然是[i,now]的答案比[i+1,now]优秀。考虑最坏情况,就是序列1 n n/2 n/4 n/8……每次值会减一半,可以分析出一个位置只会被递归到叶子log次!
算上set的复杂度,感觉应该是三个log?实际跑起来会比较玄学。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10,maxm=300000+10,inf=2000000000;
struct dong{
    int l,r,id;
} ask[maxm];
int tree[maxn*4],ans[maxm],a[maxn],sta[80];
set<int> e[maxn*4];
int i,j,k,l,r,t,n,m,mi,top;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
bool cmp(dong a,dong b){
    return a.r<b.r;
}
void build(int p,int l,int r){
    tree[p]=inf;
    if (l==r) return;
    int mid=(l+r)/2;
    build(p*2,l,mid);build(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int qr,int d){
    if (l==r){
        if (l==qr) return;
        tree[p]=min(tree[p],abs(d-a[l]));
        mi=min(mi,tree[p]);
        return;
    }
    if (r<=qr){
        set<int>::iterator it=e[p].lower_bound(d);
        if ((it==e[p].end()||abs(*it-d)>=mi)&&(it==e[p].begin()||abs(*(--it)-d)>=mi)){
            mi=min(mi,tree[p]);
            return;
        }
        int mid=(l+r)/2;
        change(p*2+1,mid+1,r,qr,d);change(p*2,l,mid,qr,d);
        tree[p]=min(tree[p*2],tree[p*2+1]);
        return;
    }
    int mid=(l+r)/2;
    if (qr<=mid) change(p*2,l,mid,qr,d);
    else{
        change(p*2+1,mid+1,r,qr,d);
        change(p*2,l,mid,qr,d);
    }
    tree[p]=min(tree[p*2],tree[p*2+1]);
}
void cr(int p,int l,int r,int a,int b){
    e[p].insert(b);
    if (l==r) return;
    int mid=(l+r)/2;
    if (a<=mid) cr(p*2,l,mid,a,b);else cr(p*2+1,mid+1,r,a,b);
}
int query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return tree[p];
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else return min(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
void write(int x){
    if (!x){
        putchar('0');
        putchar('\n');
        return;
    }
    top=0;
    while (x){
        sta[++top]=x%10;
        x/=10;
    }
    while (top){
        putchar('0'+sta[top]);
        top--;
    }
    putchar('\n');
}
int main(){
    //freopen("data.in","r",stdin);
    n=read();
    fo(i,1,n) a[i]=read();
    build(1,1,n);
    m=read();
    fo(i,1,m) ask[i].l=read(),ask[i].r=read(),ask[i].id=i;
    sort(ask+1,ask+m+1,cmp);
    r=0;
    fo(i,1,m){
        while (r<ask[i].r){
            r++;
            mi=inf;
            change(1,1,n,r,a[r]);
            cr(1,1,n,r,a[r]);
        }
        ans[ask[i].id]=query(1,1,n,ask[i].l,r);
    }
    fo(i,1,m) write(ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值