【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;
最后吐槽一句:坑爹啊,一个宝贵的上午…… 菜爆了……

浙公网安备 33010602011771号