poj2777 我的第二道线段树

第一个blog 可能写的不太好阅读  且为边想边写的 仅适合本人记录阅读

看题目

首先想到的方法就是一维数组  每次要更新的时候一个个更新就行了

但是仔细想想 和poj2828一样 很可能操作次数太大导致超时 

正好试试能不能算时间复杂度   

      命令P先不用管 基本应该就是必须遍历输出吧? 不仔细想想可能也不一定

      命令C 每次要求操作a~b  b-a就是操作次数  好了不知道怎么算  考虑最差情况

      O 100000次命令都为C  每次C要更新的区间都为1~n整个木板长度最长100000

      100000*100000 。。。。 1*10^10 在网上查貌似1000ms的运算次数约为2*10^8?(网页收藏) 超时



看网上说线段树能解决  2828是我第一道线段 这是我第二道线段 

分析看看

刚好一个一维数组 由2828知 可以认为线段树叶子结点为数组(为2828自己想 不一定正确)

线段树功能  1.高效解决连续区间的动态查询问题

             2.快速的查找某一个节点在若干条线段中出现的次数(其实仔细想想一个功能吧?)

这题可以认为是对一个线段(数组)进行快速更新(命令c)快速查询(命令o)



再分析如何降低时间复杂度既如何实现快速更新查找的


看了2828

以及http://www.cnblogs.com/shuaiwhu/archive/2012/04/22/2464583.html

2.3使用线段树的做法:



按他做的可以把一个数组分成数份 仔细如下

(结合上面网页2.3的图 先看网上图 分析怎么添加线段[1,2][3,5][4,6]在看下面分析本题)

木板长度6  每个结点i j k ij为区间k为颜色 flag = T(true)表示下面区间颜色都一样,f表示不一样

flag既为域 



初始化的树如图  下面多2个空没用我没画好而已

初始化的树和2.3的树不太一样 因为建树代码不一样,具体看poj2828 build函数

这样 

下面代码估计网上讲线段树的都有写 就是查询更新函数,类似poj2828中我写的query()

网页上的 insert()等等 下面是根据insert函数写的

1.更新 命令  C 1 3   4

查到   1,6  3<6 左子树 颜色1!=4 所以T->F

查到   1,3  1=1 3=3 所以 颜色1->4 T不变

继续往下吧 12 11 22 33 等均改为颜色4


分析到这里我发现,这更新的很复杂啊! 更新1~3 一维数组操作3次   用了二维数组竟然要更新5个结点!


明显有问题  又看了http://www.cnblogs.com/rainydays/archive/2011/05/14/2046419.html代结题报告

说:无论是更新还是查询都要遵循一个原则,当线段恰好覆盖一个节点的区间时就直接对该节操作而不再向下操作。绝对不能把区间内所有节点全部一代到底,到叶子节点。

线段树的精髓在于插入(或者更新)和查询都是从根节点开始!

所以我们无需每次涂色都更新到叶子节点。http://blog.csdn.net/wuyanyi/article/details/7032305


我想想 的确更新到叶子结点太慢了  呢么能不更新到叶子结点吗?

上面的改成这样的话            

1.更新 命令  C 1 3   4

查到   1,6  3<=3     ((6+1)/2)=3得到又边的3 左子树 颜色1!=4 所以T->F 左右子树的flag改为t

查到   1,3  1=1 3=3 所以 颜色1->4 T不变

不继续往下



这里问题在于 flag不仅仅表示下面区间是否颜色一样 还必须表示不同的操作



当我命令 P 1 2 

查到   1,6  2<=3 左子树 flag =f 一般操作

查到   1,3  2<=2所以 左子树 flag=t 开始特殊操作 以下子树颜色均为1,3的颜色 所以遇到flag=t 判断 要求查的 i,j区间在区间结点区间(a,b)内 调用fill函数在一维数组内填入颜色编号

                                                                                                                                                                          不在区间内 递归调用查询函数if(i<a&&j>b)   query(i,a)query(b,j) 具体看代码


             

当我命令 C 3 5 2

查到 1,6 5>3 3<4 所以要递归拆开来继续 拆为 3~3   和 4~5 2个线段   1!=2 flag依旧f

3~3这边

查到 1,3 3>2 所以右子树 1!=4 flag改为f   左右子树的flag改为t颜色改为父节点的颜色 4 (关键点,减少了更新次数不更新到叶子结点的代价,每次改t的结点 要对其左右孩子进行更新)

查到 3,3 3=3 3=3 所以颜色改为2 flag改为t

4~5这边

查到 4,6 5<=5 左子树 1!=2 flag改为f

查到 4,5 4==4, 5==5 所以颜色改为2 flag改为t


更新完毕   以上都为思考  实际代码还没敲 所以和代码可能略有不同


忽然发现 命令P求的是 区间内有几种颜色!  网上http://www.cnblogs.com/rainydays/archive/2011/05/14/2046419.html 用的位运算完全看不懂啊

试试自己写吧  首先想到trie树 每次new的话就++记录下多少个颜色 但是一个小功能就写trie  懒的不想写啊  也可以用color[35]

当做学习 试试用位运算

用一个32位的int型,每一位对应一种颜色,用位运算代替bool col[32]

因为题目颜色最多30种  所以可以用int32位二进制表示  00……001  表示颜色1  00……010表示颜色2 00^011表示颜色3 。。。

在上面网页中位运算有 1.query()中的return 

                                         2.countbit()


在1.中用了或运算计算是否一样不一样这加起来  如下

       颜色2  00……010

       颜色3  00……011

      或运算 00……011   然后在用2.计算32位数中有几个1 就是有几种颜色,

好像不是 因为颜色数量很少,而且父结点的颜色正好是两个子结点颜色的按位或,因此可以用位运算。最后1的个数就是不同颜色的个数。

可知 其实不是  自己用数据试过了  表示位运算并不懂,大不了直接用color[35]

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <iomanip>
#include <queue>
#include <vector>
using namespace std;
#define INF 0x3f3f3f3f
#include "stack"


#define lson u<<1
#define rson u<<1|1
#define MAX 100004
int color[32];

int L,T,O;

struct Node
{
    bool flag;
    int left,right;
    int col;
}tree[MAX<<2];

void build(int u,int l,int r)
{
    tree[u].left=l;
    tree[u].right=r;
    tree[u].col=1;
    tree[u].flag=true;
    if(l==r) return;

    int mid = (l+r)/2;
    build(lson,l,mid);
    build(rson,mid+1,r);
}

void query(int u,int l,int r)
{
    if(tree[u].left==l&&tree[u].right==r&&tree[u].flag==true)
    {
        color[tree[u].col]=1;
        return;
    }
    if(tree[u].flag)
    {

        tree[lson].col=tree[u].col;
        tree[lson].flag=true;
        tree[rson].col=tree[u].col;
        tree[rson].flag=true;

    }
    int mid = (tree[u].left+tree[u].right)/2;
    if(r<=mid)
        query(lson,l,r);
    else if(l>mid)
        query(rson,l,r);
    else
    {
        query(lson,l,mid);
        query(rson,mid+1,r);
    }
}

void updata(int u,int l,int r,int c)
{
    if(tree[u].left==l&&tree[u].right==r)
    {
        tree[u].flag=true;
        tree[u].col=c;
        return;
    }
    if(tree[u].flag)//! 关键点
    {
        tree[u].flag=false;
        tree[lson].col=tree[u].col;
        tree[lson].flag=true;
        tree[rson].col=tree[u].col;
        tree[rson].flag=true;

    }

    int mid = (tree[u].left+tree[u].right)/2;
    if(r<=mid)
        updata(lson,l,r,c);
    else if(l>mid)
        updata(rson,l,r,c);
    else
    {
        updata(lson,l,mid,c);
        updata(rson,mid+1,r,c);
    }
}

int count()
{
    int t=0;
    for(int i=0;i<32;i++)
    {
        if(color[i]==1)
            t++;
        //cout<<color[i];
    }
    //cout<<endl;
    return t;
}

int main()
{
    cin>>L>>T>>O;
    build(1,1,L);
    while(O--)
    {
        char order;
        int l,r,c;
        cin>>order;
        if(order=='C')
        {
            cin>>l>>r>>c;
            if(l>r)
                swap(l,r);
            updata(1,l,r,c);
        }
        else
        {
            cin>>l>>r;
            if(l>r)
                swap(l,r);
            memset(color,0,sizeof(color));
            query(1,l,r);
            cout<<count()<<endl;
        }
    }

    return 0;
}


ok 失败了 TLE

还是得用位运算 否则query太慢

首先学习位运算 再仔细看过之后发现上面自己写的有问题 没看全


重来一次

当做学习 试试用位运算

用一个32位的int型,每一位对应一种颜色,用位运算代替bool col[32]

因为颜色数量很少,而且父结点的颜色正好是两个子结点颜色的按位或,因此可以用位运算。最后1的个数就是不同颜色的个数。

因为题目颜色最多30种  所以可以用int32位二进制表示  00……001  表示颜色1  00……010表示颜色2 00^011表示颜色3 。。。

在上面网页中位运算有 1.updata(1,l,r,1 << (c - 1)); main函数中

                                         2.tree[u].col = tree[lson].col | tree[rson].col; updata函数中

                                         3。query函数

                                         4.countbit函数

1.成功令00……001表示 颜色1

               00……010表示 颜色2

               00……100表示 颜色3

。。。


2.实现了父节点颜色表示该子树的颜色有几种(既每个结点的col其实是指该结点及其下面孩子的所以颜色有几种,既该结点区间内有几种颜色)


3.由2  查找区间(l,r)有几种颜色只需要查找区间包含或区间等于(l,r)的树结点  取其col  这就是query函数


4.最后得到了一个二进制数  每一位代表一个颜色是否存在 用countbit查找二进制数上有几个1就行




分析一下为什么TLE


首先对比两个query代码前

原代码有庸余部分

void query(int u,int l,int r)
{
    if(tree[u].flag==true)//!不需要其他判断 因为能到达这个判断的l r必定被区间包含 不被区间包含的在下面if else已经移动到别的区间去了
    {
        color[tree[u].col]=1;
        return;
    }


    int mid = (tree[u].left+tree[u].right)/2;
    if(r<=mid)
        query(lson,l,r);
    else if(l>mid)
        query(rson,l,r);
    else
    {
        query(lson,l,mid);
        query(rson,mid+1,r);
    }
}

这是位运算的query

int query(int u,int l,int r)
{
    if (tree[u].flag)
        return tree[u].col;
    if (tree[u].left == l && tree[u].right == r)
        return tree[u].col;
    int mid = (tree[u].left + tree[u].right) / 2;
    if (r <= mid)
        return query(lson, l, r);
    else if(l > mid)
    return query(rson, l, r);


    return query(lson, l, mid) | query(rson, mid + 1, r);
}
发现很像

位运算多了一个   

<pre name="code" class="cpp">if (tree[u].left == l && tree[u].right == r)
        return tree[u].col;

 返回  可能就是这个使效率提升了 

第一个query不能 这样判断 必须

if(tree[u].left==l&&tree[u].right==r&&tree[u].flag==true)
因为如果flag为false  必须继续往下面递归得到有几种不同的颜色

而且这样写完全无意义 庸余了 直接

if(tree[u].flag==true)

就行了


然而位运算多的这段代码使得其可以在false时也直接解释 因为col就已经记录了有几种不同颜色 不需要继续递归

还有其他return 可能都减少了时间


下面ac代码


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <iomanip>
#include <queue>
#include <vector>
using namespace std;
#define INF 0x3f3f3f3f
#include "stack"




#define lson u<<1
#define rson u<<1|1
#define MAX 100004
int color[32];


int L,T,O;


struct Node
{
    bool flag;
    int left,right;
    int col;
}tree[MAX<<2];


void build(int u,int l,int r)
{
    tree[u].left=l;
    tree[u].right=r;
    tree[u].col=1;
    tree[u].flag=true;
    if(l==r) return;


    int mid = (l+r)/2;
    build(lson,l,mid);
    build(rson,mid+1,r);
}


int query(int u,int l,int r)
{
    if (tree[u].flag)    //!如果到达此结点且已经告诉你后面颜色都一样 显然不需要继续了  
                        //!能到此处显然查询区间必定在结点区间内 区间外的已经被下面的if else清走了
        return tree[u].col;
    if (tree[u].left == l && tree[u].right == r) //! 因为col记录了当前子树的颜色种类 所以不需要继续往下了
        return tree[u].col;
    int mid = (tree[u].left + tree[u].right) / 2;
    if (r <= mid)
        return query(lson, l, r);
    else if(l > mid)
    return query(rson, l, r);




    return query(lson, l, mid) | query(rson, mid + 1, r);
    //!存在类似查询区间为(3,5)时 需要区结点区间(3,3)和(4,5)合并2者col的情况
}


void updata(int u,int l,int r,int c)
{
    if(tree[u].left==l&&tree[u].right==r)//!不需要更新到叶子结点  降低时间复杂度
    {
        tree[u].flag=true;
        tree[u].col=c;
        return;
    }
    if(tree[u].flag)//! 关键点 不更新到叶子结点的代价 若父亲结点后要求被更新,就必须对下一层的孩子结点更新了
                    //!其实很想偷懒 一开始不更新 但后面要用了赶紧更新 需要注意的是一样不一定更新到叶子结点 
                    //!总的来说偷懒成功了 比更新全部快
    {
        tree[u].flag=false;
        tree[lson].col=tree[u].col;
        tree[lson].flag=true;
        tree[rson].col=tree[u].col;
        tree[rson].flag=true;


    }


    int mid = (tree[u].left+tree[u].right)/2;
    if(r<=mid)
        updata(lson,l,r,c);
    else if(l>mid)
        updata(rson,l,r,c);
    else
    {
        updata(lson,l,mid,c);
        updata(rson,mid+1,r,c);
    }
    tree[u].col = tree[lson].col | tree[rson].col;//!自己代码中没有的位运算


}


int countbit(int a)//!计算最后得到的col有几个1
{
    int x = 1;
    int ret = 0;
    for (int i = 0; i < 32; i++, x <<= 1)
        if (x & a)
            ret++;
    return ret;
}






int main()
{
    cin>>L>>T>>O;
    build(1,1,L);//!初始化树
    while(O--)
    {
        char order;
        int l,r,c;
        cin>>order;
        if(order=='C')
        {
            cin>>l>>r>>c;
            if(l>r)
                swap(l,r);
            updata(1,l,r,1 << (c - 1));//!更新树  +  位运算 具体看分析
        }
        else
        {
            cin>>l>>r;
            if(l>r)
                swap(l,r);
            //memset(color,0,sizeof(color));
            //query(1,l,r);
            printf("%d\n", countbit(query(1, l, r)));
        }
    }


    return 0;
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值