【POJ】2352 Stars

题目链接: http://poj.org/problem?id=2352

作为树状数组入门第一题,感触颇深啊!

先来闲扯几句,集训第二天晚上,merlininice师父突然兴致上来说是要教我一个很简单,却十分有用的东西——树状数组,还说在五分钟之内搞定。

“ 首先看这幅经典的图片(后来越看越不经典…),……(后来说了一大堆我完全不理解的东西,哭啊)”

 

(后来在网上找到了一幅更好理解的图,这里也挂出来:

接着,merlininice师父发了一串代码过来,还说这是树状数组的模板:

int lowbit(int x)//计算lowbit
{
    return x&(-x);
}
void add(int i,int val)
{
    while(i<=n)
    {
        a[i]+=val;
        i+=lowbit(i);
    }
}
int sum(int i)//求前i项和
{
    int s=0;
    while(i>0)
    {
        s+=a[i];
        i-=lowbit(i);
    }
    return s;
}

最后,merlininice师父居然连五分钟都没讲满就结束了,还扔给我这个题目的链接,要求第二天早上一定要秒掉……

于是乎,这就成了我学习树状数组入门的第一题……

 

学习树状数组之后的感觉就是,虽然仍旧不清楚为什么树状数组a[i]是这么求得的,但是如果仅仅把它当做工具来使用,确实蛮好用的。

add(num,del): 是将num更新为c[num]+del,并且num之后的数也在add()函数中更新了;

sum(num) :将返回前num的c[i]的和。

*******lowbit(x)是整个树状数组的灵魂所在。(所以说创造算法的实在不是用人的思维在思考的啊)***********

我曾经无聊地将前1000个x的 x&(-x) 打表打出来:

1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 64 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 128 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 64 1 2 1 4 1 2 1

8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 

16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 256 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
64 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 128 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 64 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 512 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 64 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
128 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 64 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1
16 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1
8 1 2 1 4 1 2 1 256 1 2 1 4 1 2 1 8 1 2 1

我们来看红色的数字,相信你应该很快能发现它们都是是2的指数。

再来看前100的蓝色的数字,暂时找不出规律? 那么我们先来找这些数字的下标 和 它们自身对应起来观察:

6 10  12  14  18  20  22  24  26  28  30  ……

2  2   4    2    2    4    2   8    2    4     2  ……

相信你应该已经发现了 :

6 = 2*3 -> 2;

10 = 2*5 -> 2;

12 = 2*2*3 ->4;

……

24 = 2*2*2*3 -> 8;

……

也就是说lowbit(x)的返回值是x所有因子中包含的2的指数的因子数;如果你学习过(或者了解过)谢尔排序,也许会觉得这两种完全不同的方法中却蕴含了共通的思想,即 “间距连续性规划求解问题” (介个说法纯属自创,hiahia~~)

emm…瞎扯了这么多,我还是觉得吧,树状数组就是用来求解前n项数组num[i]的和。(a[i]就是树状数组啦,或许可以称之为“辅助数组”?)

 

但是,树状数组的问题最难的,相信各位深有体会,就是如何将实际的问题转化为树状数组求和的方法。

所以第二天早上我花了将近一上午的时间才解决这道题目……

现在想想,诶,自己实在太菜了……

 

现在将当时的思路整理如下:

这个问题用来做树状数组入门实在太合适了! 因为经过一个早上的思考(太讽刺了…555),题意是按照y的非降序输入的,x又是在当y相同时按照x非降序输入的,而索要求的是问在该颗星星左下角区域(包括正左方和正下方)的星星的颗数……

现在冷静思考下,按照题意的输入方式,那么如果每输入一颗星星A(xi,yi)之后,我在X>=xi的地方都执行a[X]++,那么之后输入进来的星星B(xj,yj),sum(xj)其实就是在星星B左下方的星星颗数……

因为y已经保证是按非降顺序输入的,那么所有先输入的星星必然都在后输入的星星的下方(保证了在其下方);

那么只需通过sum()就能找到之前输入的星星的xi坐标小于等于该颗星星的颗数 (前提是保证之前的星星的xi值都已经插入了哦~)

于是输入结束后所有的答案也都出来啦!

现在想来,还真是简单 …… 囧

 

* PS:一直没有说明很重要一点就是lowbit(x)中 x!=0;  因为 0&0=0;所以在调用add(num,del)的时候必须保证num!=0。(这点很重要哦~)

         所以在本题星星的xi上要做一些处理(很多情况下是使每一颗星星的xi++)

下面是代码的主体部分

View Code
 1  while(cin>>m){
 2         memset(a,0,sizeof(a));
 3         memset(level,0,sizeof(level));
 4           for(i=0; i<m; ++i){
 5                 cin>>x>>y;
 6                 add(x+1,1);
 7                 star = sum(x+1)-1;
 8                 level[star]++;
 9           }
10           for(i=0; i<m; ++i)
11                 cout<<level[i]<<endl;

 

最后吐槽一句:坑爹啊,一个宝贵的上午…… 菜爆了……

          

posted on 2012-07-21 11:03  Yuna_  阅读(95)  评论(0)    收藏  举报