【XSY3404】猴戏世家

法一:

题解做法。不太好想。

动态维护不太好做,我们考虑先把栅栏最后长什么样维护出来。

扫描线,按 \(x\) 从大到小扫描,过程中维护当前扫描线上的一些区间,每一个区间内的点同属于一个栅栏,然后扫描到一个栅栏的时候找到它下面的第一个区间:如果这个区间对应的栅栏出现时间比当前栅栏晚,那么之后这个区间会属于当前栅栏,那么合并当前栅栏的区间和这个区间,并继续往下找;如果这个区间对应的栅栏出现时间比当前栅栏早,那么当前栅栏延伸到这个区间时就会被挡住了。

于是我们就能求出最后每一个点属于哪一个栅栏,维护区间可以用 set维护。

但这和我们要求的真实的答案是不同的,考虑按时间从后往前删除栅栏及其影响,那么枚举到某个栅栏时当前它的答案就是它真实的答案(因为出现时间比它晚的栅栏的影响都被删除了)。假设当前要删除栅栏 \(x\),设未被删除的栅栏中包含 \(x\) 的最小的栅栏为 \(f\),那么删除 \(x\)\(x\) 所包含的点变成被 \(f\) 包含,需要把贡献加到 \(f\) 上,过程可以用并查集维护。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

#define N 300010
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)

using namespace std;

inline 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<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x*f;
}

struct Point
{
    int x,y,id;
}p[N<<1];

bool operator < (Point a,Point b)
{
    if(a.x==b.x) return a.id>b.id;
    return a.x>b.x;
}

int n,m;
int cnt;
int inc[N],fa[N],sum[N],ans[N];

set<pii>s;

int find(int x)
{
    return x==fa[x]?x:(fa[x]=find(fa[x]));
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        p[++cnt].x=read(),p[cnt].y=read(),p[cnt].id=0;
    m=read();
    for(int i=1;i<=m;i++)
        p[++cnt].x=read(),p[cnt].y=read(),p[cnt].id=i;
    sort(p+1,p+cnt+1);
    for(int i=1;i<=cnt;i++)
    {
        if(p[i].id)
        {
            auto tmp=s.lower_bound(mk(p[i].y,114514));
            if(tmp!=s.end()) inc[p[i].id]=(*tmp).se;
            while(1)
            {
                tmp=s.lower_bound(mk(p[i].y,114514));
                if(tmp==s.begin()) break;
                tmp--;
                if((*tmp).se<p[i].id) break;
                s.erase(tmp);
            }
            s.insert(mk(p[i].y,p[i].id));
        }
        else
        {
            auto tmp=s.lower_bound(mk(p[i].y,-114514));
            if(tmp!=s.end()) sum[(*tmp).se]++;
        }
    }
    for(int i=1;i<=m;i++) fa[i]=i;
    for(int i=m;i>=1;i--)
    {
        ans[i]=sum[i];
        int x=find(inc[i]);
        if(x) sum[x]+=sum[i];
        fa[i]=x;
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",sum[i]);
    return 0;
}

法二:

可能比较直接,但大代码量+及其卡常。

考虑直接按题意按时间维护:

我们对当前已经加入的每个栅栏开一棵线段树维护这个栅栏当前所控制的点,线段树内以 \(x\) 坐标为下标(动态开点)。

假设当前要插入栅栏 \(i=(x_0,y_0)\),我们先找到 \((x_0,y_0)\) 在哪个栅栏的控制范围内,设其为栅栏 \(j\)

那我们就是要把栅栏 \(j\) 所控制的点分为两个部分:\(x\leq x_0\)\(y\leq y_0\) 的点属于栅栏 \(i\),其他点仍属于栅栏 \(j\)

我们可以在栅栏 \(j\) 所对应的线段树上进行分裂:首先 \(x>x_0\) 的部分可以直接分裂出来,对于 \(x\leq x_0\) 的部分我们继续在线段树 dfs:对于当前点 \(u\),若 \(ymax_u\leq y_0\)\(ymin_u>y_0\),那么当前子树属于哪个已经确定了,直接退出即可,否则继续 dfs。

这样看起来复杂度没法保证,但实际上是正确的:设分裂之后的两棵线段树分别为 \(A\)\(B\),那么分裂的复杂度和两者合并的复杂度相等。那么总的复杂度就可以看做是所有栅栏加入之后划分出的若干个区域所对应的若干棵线段树合并到一起的复杂度,为 \(O(n\log n)\)

常数极大,踩线过的。

#include<bits/stdc++.h>

#define N 300010
#define NN 15000010
#define lc(u) ch[u][0]
#define rc(u) ch[u][1]
#define INF 0x7fffffff

using namespace std;

namespace IO{
    char buf[1000010],*cur=buf+1000010;
    inline char getc(){
        (cur==buf+1000010)?fread(cur=buf,1,1000010,stdin):0;
        return *cur++;
    }
    char buff[1000010],*curr=buff;
    inline void flush(){
        fwrite(buff,1,curr-buff,stdout);
    }
    inline void putc(const char &ch){
        (curr==buff+1000010)?fwrite(curr=buff,1,1000010,stdout):0;
        *curr++=ch;
    }  
    inline void rdi(int &x){
        x=0;char ch=getc();
        while(ch<'0'||ch>'9')ch=getc();
        while(ch>='0'&&ch<='9'){
            x=(x<<1)+(x<<3)+(ch^'0');
            ch=getc();
        }
    }
    char st[60];int tp;
    void PT(int x){
        if(x==0)putc('0');
        else{
            while(x>0){
                st[++tp]=x%10+'0';
                x/=10;
            }
        }
        while(tp)putc(st[tp--]);
    }
}using IO::getc;using IO::putc;using IO::rdi;using IO::PT;

struct Point
{
    int x,y,id;
}p[N<<1];

bool operator < (Point a,Point b)
{
    if(a.x==b.x) return !a.id;
    return a.x<b.x;
}

int n,m,cnt,pos[N],px[N],py[N];
int node,ch[NN][2],ymin[NN],ymax[NN],sum[NN],minn[NN];
int rt[N];

inline void up(const int u)
{
    ymin[u]=min(ymin[lc(u)],ymin[rc(u)]);
    ymax[u]=max(ymax[lc(u)],ymax[rc(u)]);
    sum[u]=sum[lc(u)]+sum[rc(u)];
    minn[u]=min(minn[lc(u)],minn[rc(u)]);
}

void build(int &u,int l,int r)
{
    u=++node;
    if(l==r)
    {
        ymin[u]=ymax[u]=p[l].y;
        if(p[l].id) minn[u]=p[l].id,pos[p[l].id]=l;
        else sum[u]=1,minn[u]=INF;
        return;
    }
    int mid=(l+r)>>1;
    build(lc(u),l,mid);
    build(rc(u),mid+1,r);
    up(u);
}

inline void update(int u,int l,int r,int x)
{
    if(l==r)
    {
        minn[u]=INF;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) update(lc(u),l,mid,x);
    else update(rc(u),mid+1,r,x);
    minn[u]=min(minn[lc(u)],minn[rc(u)]);
}

int x,y;

inline void split(const int u,const int l,const int r,int &a,int &b)
{
    if(!u)
    {
        a=b=0;
        return;
    }
    if(r<=x&&ymax[u]<=y)
    {
        a=u,b=0;
        return;
    }
    if(l>x||ymin[u]>y)
    {
        a=0,b=u;
        return;
    }
    a=++node,b=u;
    const int mid=(l+r)>>1;
    split(lc(u),l,mid,lc(a),lc(b));
    split(rc(u),mid+1,r,rc(a),rc(b));
    if(!lc(a)&&!rc(a)) a=0;
    else up(a);
    up(b);
}

int main()
{
    ymin[0]=INF,ymax[0]=-INF,sum[0]=0,minn[0]=INF;
    rdi(n);
    for(int i=1;i<=n;i++) cnt++,rdi(p[cnt].x),rdi(p[cnt].y);
    rdi(m);
    for(int i=1;i<=m;i++) cnt++,rdi(p[cnt].x),rdi(p[cnt].y),p[cnt].id=i;
    sort(p+1,p+cnt+1);
    for(int i=1;i<=cnt;i++)
        if(p[i].id) px[p[i].id]=i,py[p[i].id]=p[i].y;
    build(rt[1],1,cnt);
    for(int i=1;i<=m;i++)
    {
        update(rt[i],1,cnt,pos[i]);
        x=px[i],y=py[i];
        if(sum[rt[i]])
        {
            int rta,rtb;
            split(rt[i],1,cnt,rta,rtb);
            if(rta&&minn[rta]!=INF) rt[minn[rta]]=rta;
            if(rtb&&minn[rtb]!=INF) rt[minn[rtb]]=rtb;
            PT(sum[rta]);putc('\n');
        }
        else putc('0'),putc('\n');
    }
    IO::flush();
    return 0;
}
posted @ 2022-10-30 12:19  ez_lcw  阅读(20)  评论(0编辑  收藏  举报