• poj 2528 Mayor's posters


    线段树(经典题)

    离散化+成段更新+区间统计

    题意:先输入case数,每个case输入n,表示下面有n个海报,每行就是海报的左右坐标,第i个海报的颜色为i。一面墙长度固定为10000000,问这些海报贴上去后能看到多少种颜色

    这个问题的难处其实是怎么离散化(也就是映射,映射后就是简单的线段树整段更新,最后区间询问)。今天第一次学离散化,说说个人的解法

    方法是和别人的大概一样的,可能代码实现有些不同,我的代码可能空间开销更大些(后来查看代码,很多空间能省略,但是下面的代码是我最原始的代码,并没有后来的优化,就以此来讲解)。

    int s[MAXN][2];  保存原始的数据,[0]是起点坐标,[1]是终点坐标,那么一共产生2*n个端点

    struct point
    {
    int a,n,f; //端点坐标,属于哪条线段,起点或者终点
    }p[2*MAXN];

    所以把2*n个端点逐一放入p数组中,并且要记录这个端点是来自哪条线段(n这个域),在这条线段中是起点还是终点(f这个域)

    然后对p数组排序,以端点坐标大小排序(a这个域)

    接下来是映射,例如排序后的结果为

    10,21,38,40,40,59

    映射为

    1,2,3,4,4,5

    也就是说按数字大小映射,而且也可以发现,最后的5其实也代表了有多少个不同的数字

    这个映射的结果一定包含[1,m]的所有整数,等下我们要建线段树的时候,总区间的长度就是[1,m]

    我RE了很多次,就是搞错了m的大小范围,m最大可以去到80000(60000也行),80000怎么来的,是2*10000*4

    因为海报的个数最多10000,有20000个端点,极端情况下,这20000个点都不一样,映射过去的话,就是[1,20000],也就是m最大可以是m

    回想一般情况下,线段树的总区间为[1,m]的话,我们开辟线段树的数组一般为4*m(其实开到3*m就足够了),所以这就是为什么80000的原因

    说回头,我们扫过p数组的时候,没得到一个点,就先映射它的坐标(和它前面那个端点坐标比较是否不同,不同的话,就是一个新的点,具体看代码),然后看它是来自哪条线段(映射后同样是这条线段),再看它在原线段中是起点还是终点,然后相对应地记录现在线段的起点和终点

    最后映射后的线段信息全部在下面的数组中

    struct interval //离散化后的线段
    {
    int l,r,col;
    }in[MAXN];

    得到了它,就可以建树,整段更新,查询了

    /*
    处理的主要问题就是如何映射,映射结束后,就是整段区间更新,最后询问总区间有多少种不同的颜色
    */
    
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define MAXN 10010
    
    using namespace std;
    
    bool used[MAXN]; //询问时记录哪些颜色已经被计数
    int s[MAXN][2] , N; //s数组记录原始数据,不过可以省略这个数组
    struct point 
    {
        int a,n,f; //端点坐标,属于哪条线段,起点或者终点
    }p[2*MAXN];
    struct interval //离散化后的线段
    {
        int l,r,col;
    }in[MAXN];
    struct segment //线段树结点
    {
        int l,r,col,val;
    }t[8*MAXN];
    
    int query(int a ,int b ,int rt)
    {
        int col=t[rt].col;
        if(t[rt].val) //单色
        {
            if(!used[col]) //此颜色没被用过
            { 
                used[col]=1;
                return 1; 
            }
            else  //已经被用过
                return 0;
        }
        int mid=(t[rt].l+t[rt].r)>>1;
        /*
        if(a>mid) //右孩子
            return query(a,b,rt<<1|1);
        else if(b<=mid) //左孩子
            return query(a,b,rt<<1);
        else //左右孩子
        */
            return query(a,mid,rt<<1)+query(mid+1,b,rt<<1|1);
    }
    
    void updata(int a ,int b ,int col ,int rt)
    {
        if(t[rt].val && t[rt].col==col) return ;
        //小剪枝,当前区间单色且与要更改的区间颜色相同则返回不用深入
        if(t[rt].l==a && t[rt].r==b) //目标区间
        {
            t[rt].col=col; t[rt].val=1;  //单色
            return ;
        }
        if(t[rt].val) //单色,传递给左右孩子
        {
            t[rt<<1].val=t[rt<<1|1].val=t[rt].val;
            t[rt<<1].col=t[rt<<1|1].col=t[rt].col;
            t[rt].val=0; //修改为不是单色
        }
        int mid=(t[rt].l+t[rt].r)>>1;
        if(a>mid) //访问右孩子
            updata(a,b,col,rt<<1|1);
        else if(b<=mid) //访问左孩子
            updata(a,b,col,rt<<1);
        else //左右均访问
        {
            updata(a,mid,col,rt<<1);
            updata(mid+1,b,col,rt<<1|1);
        }
    }
    
    void build(int a ,int b ,int rt)
    {
        t[rt].l=a; t[rt].r=b; t[rt].val=0; t[rt].col=0; //不是单色的
        if(a==b) return ;
        int mid=(a+b)>>1;
        build(a,mid,rt<<1);
        build(mid+1,b,rt<<1|1);
    }
    
    int cmp(struct point x ,struct point y)
    {
        return x.a<y.a?1:0;
    }
    
    int main()
    {
        int Case;
        scanf("%d",&Case);
        while(Case--)
        {
            scanf("%d",&N);
            for(int i=1; i<=N; i++) 
            {
                scanf("%d%d",&s[i][0],&s[i][1]);
                p[2*i-1].a=s[i][0]; p[2*i-1].n=i; p[2*i-1].f=0;
                p[2*i].a=s[i][1];   p[2*i].n=i;   p[2*i].f=1;
            }
            sort(p+1,p+2*N+1,cmp);
            p[0].a=p[1].a;  //为了下面的循环要设一个特殊值
            int m=1 , n ,f;  //表示有多少个不同的数字,也就是映射使用的
            for(int i=1; i<=2*N; i++)
            {
                n=p[i].n , f=p[i].f;
                if(p[i].a!=p[i-1].a) m++;
                if(!f) //起点
                { in[n].l=m; in[n].col=n; }
                else  //终点
                { in[n].r=m; in[n].col=n; }
            }
            //for(int i=1; i<=N; i++) printf("[%d , %d] %d\n",in[i].l,in[i].r,in[i].col);
            /*
            至此,离散化已经结束
            接下来就是建树
            然后利用已经保存好的区间去整段更新
            最后加上一个查询即可
            */
            build(1,m,1);  //整个线段树的区间长度[1,m]
            for(int i=1; i<=N; i++)
                updata(in[i].l, in[i].r, in[i].col, 1);
            memset(used,0,sizeof(used));
            printf("%d\n",query(1,m,1));
        }
        return 0;
    }
  • 相关阅读:
    【原创】C#初级教程学习笔记004-流程控制
    【原创】C#初级教程学习笔记002-进入C#编程
    kettle迁移简单案例
    Pycharm切换Git分支
    pycharm新文件增加默认注释
    pytest-mark标签装饰器
    python生成requirements.txt
    Cyber GIS
    mybatis之foreach用法_(搬运)
    Python 十进制、二进制、八进制、十六进制的转化
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2923923.html
Copyright © 2020-2023  润新知