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;
}

 

posted @ 2013-02-24 00:43  Titanium  阅读(1774)  评论(0编辑  收藏  举报