【splay+扫描线】CERC2017-B-Buffalo Barricades

【题目】
原题地址
题目大意:给出 n 个点 ( x , y ) ,有 q 个操作 ( a , b ) ,先求出有多少个不被栅栏挡住的点满足 x <= a 并且 y <= b ,然后以 ( a , b ) 这个点作为右上角形成一个7字型的栅栏,遇到之前的栅栏或者坐标轴就结束。

【题目分析】
一开始看这道题,感觉是线段树在 x 轴和 y 轴维护交点之类的,然后容斥计算贡献。但是想着想着发现这个交点实在是维护不了,如果用二维线段树倒是可以,不过MLE得飞起。
接着想用splay维护一下一个楼梯形轮廓,不过还是没有好的思路。
于是弃疗看题解,发现居然就只是一个 s p l a y +扫描线,太厉害了!

【解题思路】
如果我们能处理出每个围栏区间内有多少个贡献点,那么我们只需要倒序移走围栏,用DSU或者dp维护贡献就可以得到最后答案。
我们考虑将所有节点按 y 从大到小排序,那么如果我们遇到一个围栏节点,等价于在 x 这个位置插入一个节点,
而遇到一个贡献点,等价于在 x 这个位置后面找一个最近的围栏节点。
因此这个东西可以用 s p l a y 来维护。

接下来如果我们只考虑两个点 ( x , y , t 1 ) ( a , b , t 2 ) ,其中 x < a , y > b ,那么会有两种围栏的情况。
t 1 < t 2 ,则我们遇到 ( a , b , t 2 ) 时仍然会在splay保留 ( x , y , t 1 )
t 2 > t 1 ,则接下来,我们遇到的所有贡献点不会对 ( x , y , t 1 ) 产生贡献,因此可以删去这个点。
这里写图片描述
这是遇到围栏点的情况,遇到贡献点的话我们直接在 s p l a y 中找到 x 比它大的第一个围栏点即可。
最后的DSU和dp维护要注意合并的顺序,是大的t往小的t进行合并。

【参考代码】

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

const int N=3e5+10;
const int M=N*4;
int n,m,tot;
int las[N],cnt[N],f[N],ans[N];

struct Tdata
{
    int x,y,id;
    Tdata(){}
    Tdata(int x,int y,int id):x(x),y(y),id(id){}
}a[M];

bool cmp(Tdata A,Tdata B)
{
    if(A.y^B.y)
        return A.y>B.y;
    return A.x<B.x;
}

struct Tele
{
    int x,t;
    Tele(){}
    Tele(int x,int t):x(x),t(t){}
    bool operator <(const Tele &A)const{
        if(x^A.x)
            return x<A.x;
        return t<A.t;
    }
};

struct Splay
{
    int root,len;
    int ch[M][2],fa[M];
    Tele val[M];

    void rotate(int x)
    {
        int y=fa[x],z=fa[y],kind=ch[fa[x]][1]==x,t=ch[x][!kind];
        ch[y][kind]=t;ch[x][!kind]=y;
        if(t)
            fa[t]=y;
        fa[x]=z;
        if(z)
            ch[z][ch[fa[y]][1]==y]=x;
        fa[y]=x;
    }

    void splay(int x)
    {
        while(fa[x])
        {
            int y=fa[x];
            if(fa[y])
            {
                if(ch[fa[x]][1]==x^ch[fa[y]][1]==y)
                    rotate(x);
                else
                    rotate(y);
            }
            rotate(x);
        }
        root=x;
    }

    void insert(int x,int t)
    {
        val[++len]=Tele(x,t);
        if(!root)
        {
            root=len;
            return;
        }
        int now=root;
        while(true)
        {
            if(val[len]<val[now])
            {
                if(!ch[now][0])
                {
                    ch[now][0]=len;
                    fa[len]=now;
                    break;
                }
                now=ch[now][0];
            }
            else
            {
                if(!ch[now][1])
                {
                    ch[now][1]=len;
                    fa[len]=now;
                    break;
                }
                now=ch[now][1];
            }
        }
        splay(len);
    }

    int findpre()
    {
        int x=ch[root][0];
        while(ch[x][1])
            x=ch[x][1];
        return x;
    }

    int findsuc()
    {
        int x=ch[root][1];
        while(ch[x][0])
            x=ch[x][0];
        return x;
    }

    void reset(int x)
    {
        ch[x][0]=ch[x][1]=fa[x]=0;
        val[x].x=val[x].t=0;
    }

    void dele(int x)
    {
        splay(x);
        if(!ch[x][0] || !ch[x][1])
        {
            root=ch[x][0]+ch[x][1];
            fa[root]=0;
            reset(x);
            return;
        }
        int now=findsuc();
        splay(now);ch[now][0]=ch[x][0];
        fa[ch[now][0]]=now;
        reset(x);
    }
}tr;

int findf(int x)
{
    return f[x]?f[x]=findf(f[x]):x;
}

void merge(int x,int y)
{
    x=findf(x);y=findf(y);
    if(x^y)
        f[x]=y,cnt[y]+=cnt[x];
}

int main()
{
    freopen("CERC2017B.in","r",stdin);
    freopen("CERC2017B.out","w",stdout);

    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        a[i]=Tdata((x<<1)-1,(y<<1)-1,0);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        a[i+n]=Tdata(x<<1,y<<1,i);
    }
    tot=n+m;
    sort(a+1,a+tot+1,cmp);

    for(int i=1;i<=tot;++i)
    {
        tr.insert(a[i].x,a[i].id);
        if(a[i].id)
        {
            int suc=tr.findsuc();
//printf("%d\n",suc);
            if(suc)
                las[a[i].id]=tr.val[suc].t;
            while(true)
            {
                int pre=tr.findpre();
                if(!pre || tr.val[pre].t<a[i].id)
                    break;
                tr.dele(pre);
            }
        }
        else
        {
            int suc=tr.findsuc();
//printf("!%d\n",suc);
            tr.dele(tr.root);
            if(suc)
                cnt[tr.val[suc].t]++;
        }
    }

    for(int i=m;i;--i)
    {
        ans[i]=cnt[findf(i)];
        if(las[i])
            merge(i,las[i]);
    }
    for(int i=1;i<=m;++i)
        printf("%d\n",ans[i]);

    return 0;
}

【总结】
其实想的方向是没有什么问题的,不过还是欠缺一点思路转换。
然后就是代码能力太弱了,一个splay两个小时才AC,太菜了。
省赛前要多打数据结构,提高代码能力!

猜你喜欢

转载自blog.csdn.net/dream_lolita/article/details/79920908