【二分答案+线段树+平衡树/线段树分治】APIO2018新家

【题目】
原题地址
题目大意:太长了去看题面吧。

【题目分析】
一道看上去比较奇怪的题目,需要一定转化思想。
不过二分答案这个点还是比较显然的。

【解题思路】
对时间扫描的话,每间商店等价于插入操作和删除操作。
问题转化为支持插入/删除,询问以某个位置为重心包含所有不同数字的最小长度。
对于询问,显然我们可以二分答案。但是如何查询一个区间内是否出现所有种类的数?
考虑出现的充要条件,以及出现的数与前后的关联。
可以发现,若一个数 x x [l,r]中出现,且在 [r+1,+oo] [ r + 1 , + o o ] 中出现,对于 [r+1,+oo] [ r + 1 , + o o ] 中的每一个 x x ,它的上一次出现位置必然>=l
因此我们可以维护一个平衡树来支持前驱后继的查找。
对于每一个时间位置,我们要维护的就是每个数字上一次出现的位置,并取这些位置中的最小值作为这个位置的权值。
这样我们就可以通过查询 [r+1,+oo] [ r + 1 , + o o ] 中的最小值来判断了。(当然在末尾需要多放每个数字)

如果我们开一棵全局线段树,对于序列的每个位置再维护一个堆,就可以用 O(nlog2n) O ( n l o g 2 n ) 的复杂度完成这道题。
但是这里发现这个堆是不必要的,因为对于同一编号的商店各个商店位置不同(如果相同的话看时间维,时间维有交可合并,不交不影响,其实交也不影响),我们完全可以只用一棵线段树,记录当前下标的 pre p r e ,并用 set s e t 维护即可,时间复杂度还是 O(nlog2n) O ( n l o g 2 n )

这里有一种一个 log l o g 的 做 法 解法在这,直接贴了:
考虑对每个询问二分答案
如果询问坐标为 x x ,答案m等价于存在一种店在 (xm,x+m) ( x − m , x + m ) 不存在店,等价于在 [x+m,inf) [ x + m , i n f ) 存在店前驱 xm ≤ x − m
对每种店开个 set s e t ,对全局开个线段树维护区间最小前驱即可两个 log l o g
在线段树上二分即可一个 log l o g

具体说一下。
你要求的是最大的 i i ,满足 记[i,inf)的最小前驱为 mn m n ,则 mn+i2×x m n + i ≤ 2 × x
无解的情况先特判掉。
假如你现在在线段树上的 [l,r] [ l , r ]
如果 x x 落在[mid+1,r],那么 i i 一定落在[mid+1,r]
如果 x x 落在[1,mid],那么你需要判断最终的答案会不会落在 [mid+1,r] [ m i d + 1 , r ] 上。
由于 i i 越小,mn越小,所以你只用检查一下 mid+1 m i d + 1 是否满足 mn+i2×x m n + i ≤ 2 × x 即可。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10;
const int INF=1e9+10;
int n,m,qs,cnt,cn;
int ans[N];
set<int>mp[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){ret=ret*10+(c^48);c=getchar();}
    return f?ret:-ret;
}

void write(int x)
{
    if(x<0)x=-x,putchar('-');
    if(x>9)write(x/10);
    putchar(x%10^48);
}

struct Tshop
{
    int x,t,l,r;
};
Tshop a[N];

bool cmp(Tshop A,Tshop B)
{
    return A.x<B.x;
}

struct Tquery
{
    int op,pos,x,t;
    Tquery(){}
    Tquery(int opt,int pp,int xx,int tt){
        op=opt;pos=pp;x=xx;t=tt;
    }
};
Tquery q[N];

bool cmpq(Tquery A,Tquery B)
{
    if(A.t^B.t)
        return A.t<B.t;
    return A.op<B.op;
}

struct segment
{
    int mi[N<<3];

    void pushup(int x)
    {
        mi[x]=min(mi[x<<1],mi[x<<1|1]);
    }

    void build(int x,int l,int r)
    {
        if(l==r)
        {
            mi[x]=(l<=n)?INF:0;
            return;
        }
        int mid=(l+r)>>1;
        build(x<<1,l,mid);build(x<<1|1,mid+1,r);
        pushup(x);
    }

    void update(int x,int l,int r,int p,int val)
    {
        if(l==r)
        {
            mi[x]=val;
            return;
        }
        int mid=(l+r)>>1;
        if(p<=mid)
            update(x<<1,l,mid,p,val);
        else
            update(x<<1|1,mid+1,r,p,val);
        pushup(x);
    }

    int query(int x,int l,int r,int p)
    {
        if(a[l].x>p)
            return mi[x];
        int mid=(l+r)>>1,ret=INF;
        if(a[mid].x>p)
            ret=min(ret,query(x<<1,l,mid,p));
        ret=min(ret,query(x<<1|1,mid+1,r,p));
        return ret;
    }
}tr;

void init()
{
    n=read();m=read();qs=read();
    for(int i=1;i<=n;++i)
    {
        a[i].x=read();a[i].t=read();
        a[i].l=read();a[i].r=read();        
    }
    sort(a+1,a+n+1,cmp);

    for(int i=1;i<=n;++i)
    {
        q[++cnt]=Tquery(1,0,i,a[i].l);
        q[++cnt]=Tquery(3,0,i,a[i].r);
    }
    for(int i=1;i<=qs;++i)
    {
        int x=read(),t=read();
        q[++cnt]=Tquery(2,i,x,t);
    }   
    sort(q+1,q+cnt+1,cmpq);

    //for(int i=1;i<=n;++i)
        //printf("%d %d %d %d\n",a[i].x,a[i].t,a[i].l,a[i].r);  
    //for(int i=1;i<=cnt;++i)
        //printf("%d %d %d %d\n",q[i].op,q[i].pos,q[i].x,q[i].t);

    for(int i=1;i<=m;++i)
    {
        mp[i].insert(0);mp[i].insert(i+n);
        a[i+n].x=INF;
    }
}

void solve()
{
    cn=n+m;tr.build(1,1,cn);
    for(int i=1;i<=cnt;++i)
    {
        if(q[i].op==1)
        {
            int x=q[i].x;mp[a[x].t].insert(x);
            set<int>::iterator lp=mp[a[x].t].lower_bound(x),rp=mp[a[x].t].upper_bound(x);
            if(lp!=mp[a[x].t].begin())
                --lp;   
            //printf("%d %d\n",*lp,*rp);
            tr.update(1,1,cn,x,*lp);tr.update(1,1,cn,*rp,x);
        }
        else
        if(q[i].op==3)
        {
            int x=q[i].x;mp[a[x].t].erase(x);
            set<int>::iterator lp=mp[a[x].t].lower_bound(x),rp=mp[a[x].t].upper_bound(x);
            if(lp!=mp[a[x].t].begin())
                --lp;
            //printf("%d %d\n",*lp,*rp);
            tr.update(1,1,cn,x,INF);tr.update(1,1,cn,*rp,*lp);
        }
        else
        {
            int x=q[i].x,l=0,r=INF,ret=INF;
            while(l<r)
            {
                int mid=(l+r)>>1;   
                //printf("%d\n",mid);
                int fl=max(1,x-mid),fr=min(INF-1,x+mid);
                if(a[tr.query(1,1,cn,fr)].x>=fl)
                    r=mid,ret=mid;
                else
                    l=mid+1;
            }
            //printf("ret:%d\n",ret);
            ans[q[i].pos]=(ret==INF)?-1:ret;
        }
    }
    for(int i=1;i<=qs;++i)
        write(ans[i]),puts("");
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("LOJ2585.in","r",stdin);
    freopen("LOJ2585.out","w",stdout);
#endif
    init();
    solve();

    return 0;
}

【总结】
平衡树维护前驱,以及线段树分治的思想都很优秀,这道题目有很多值得借鉴的思想,是一道很好的题目qwq。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值