[題解][樹狀數組] POJ 2352 - Stars 樹狀數組的簡單應用
VJudge題目:https://cn.vjudge.net/contest/283317#problem/A
即POJ 2352 - Stars:http://poj.org/problem?id=2352
題目要求:輸入所有星星的坐標,處於某一星星左下方(包括左與下)的有多少星星,就算它有幾級。如果它左下沒有星星就算0級。現在要求你統計各等級的星星各有多少顆。
輸入輸出:一個數字N(1<=N<=15000),代表星星的數目;然後輸入N個坐標(0<=X,Y<=32000)。坐標已按Y
示例:
Input :
5
1 1
5 1
7 1
3 3
5 5
Output :
1
2
1
1
0
你可以理解為在一個坐標系中,對於某一星星(X,Y),其等級數=坐標(x<=X,y<=Y)的星星的數目。想象這個星星到坐標軸的垂線把裏面的星星圍起來的樣子,圍了多少顆星就算它幾級。
註意題目的輸入已經是升序排列的了,Y坐標相同的星星,按X坐標排列,那麽對於某個Y坐標的星星,前面有多少相同Y坐標的星星,它至少就有幾級。比如示例中的(1,1)(5,1)(7,1),基礎等級依次是0,1,2。問題在於,如何查找X坐標<=該星星的其他星星?
看到這裏很容易想出一種思路:每輸入一行星星(Y
既然已經知道Y是升序的,那麽讀入的Y可以不用管,只需要尋找小於X的星星即可。開一個數組count[32002]={0},每次讀入一個X,就計算count[0]到count[X]的總和,作為這個星星的等級,然後count[X]+1。但是每次對區間求和,都會因為循環浪費時間。
在1000ms的限制內,必須使用高效的查詢方法來實現這個求和(即統計這些星星的個數)。
這道題是樹狀數組的模板題,容易找到這道題的AC
樹狀數組的講解見:https://www.cnblogs.com/acgoto/p/8583952.html
https://blog.csdn.net/FlushHip/article/details/79165701#commentBox
代碼來自:https://www.cnblogs.com/kuangbin/archive/2012/08/09/2630072.html
蒟蒻的代碼基本就是上面鏈接裏的樣子,而且沒有寫註釋,所以去看這個代碼就好了。復制如下。另外拿了樹狀數組的圖來解釋一下。
1 /*
2 POJ 2352 Stars
3 就是求每個小星星左小角的星星的個數。坐標按照Y升序,Y相同X升序的順序給出
4 由於y軸已經排好序,可以按照x坐標建立一維樹狀數組
5 */
6 #include<stdio.h>
7 #include<iostream>
8 #include<algorithm>
9 #include<string.h>
10 using namespace std;
11 const int MAXN=15010;
12 const int MAXX=32010;
13 int c[MAXX];//樹狀數組的c數組
14 int cnt[MAXN];//統計結果
15 int lowbit(int x)
16 {
17 return x&(-x);
18 }
19 void add(int i,int val)
20 {
21 while(i<=MAXX)
22 {
23 c[i]+=val;
24 i+=lowbit(i);
25 }
26 }
27 int sum(int i)
28 {
29 int s=0;
30 while(i>0)
31 {
32 s+=c[i];
33 i-=lowbit(i);
34 }
35 return s;
36 }
37 int main()
38 {
39 //freopen("in.txt","r",stdin);
40 //freopen("out.txt","w",stdout);
41 int n;
42 int x,y;
43 while(scanf("%d",&n)!=EOF)
44 {
45 memset(c,0,sizeof(c));
46 memset(cnt,0,sizeof(cnt));
47 for(int i=0;i<n;i++)
48 {
49 scanf("%d%d",&x,&y);
50 //加入x+1,是為了避免0,X是可能為0的
51 int temp=sum(x+1);
52 cnt[temp]++;
53 add(x+1,1);
54
55 }
56 for(int i=0;i<n;i++)
57 printf("%d\n",cnt[i]);
58 }
59 return 0;
60 }
這三個函數是維護樹狀數組用的。從這幅圖看得出來,這裏是通過存儲後綴和(吧?)來提高求和速度的。2^15=32768>32000,意味著本題更新C[]數組中一個數最多只需更新16層(16個數),就可以保證後綴和的一致性。而求和時即使是最大的數組下標2^15-1=32767=0111,1111,1111,1111,也僅需對15個後綴和求和。這個數組做到高效地更新和求和。
不理解樹狀數組,就這幅圖簡單地解釋下:
(count數組是上文的星星統計數,而非代碼中的cnt,代碼中cnt是等級統計數)
(count數組即兩篇講解中的基礎數組A[])
(原代碼中的c數組被我寫為講解中的大寫C)
以0100(4),0110(6),0111(7),1000(8) 為例,取這些數的最低位1,會變成100(4),10(2),1,1000(8),這表示在C[]數組中,C[4]存儲了count[4][3][2][1]4個數的和,C[6]存儲了count[6][5]2個數的和,C[7]只有count[7]1個數,C[8]就是count[8]-[1]8個數了。
至於求count[0]至count[X]的總和,以0110(6),0111(7),01011(11)為例,同樣看這幅圖,sum(6)=C[6]+C[4],用二進制來看是C[0110]+C[0100] (+C[0000]) ;
sum(7)=C[7]+C[6]+C[4],二進制C[0111]+C[0110]+C[0100] (+C[0000]) ;
sum(11)=C[11]+C[10]+C[8] ==> C[01011]+C[01010]+C[01000] (+C[00000]) ;
你會發現,從左往右每一項中二進制的最低位1被替換為0,直至變成整個二進制數字變為0。
在這道題中樹狀數組只有這兩個用途,因此只需要寫兩個函數,更新後綴和與求和。另外一個函數lowbit用於取最低位,直接寫進兩函數裏也可以。不過為了程序整潔寫作一個函數比較好。
由於讀入一個星星只需對統計數+1,此處僅僅維護了樹狀數組而沒有保存原來的基礎數組。
lowbit()不再解釋;
add():讀入一個星星如(5,5)後,X為5的星星統計數count[5]++。那麽在樹狀數組中,C[5]本身要+1,父層C[6]+1,C[8]、C[16]、C[32]等統統+1。lowbit保證它能正常取父層,而非簡單地取C[2^n]。
由於此題所謂樹狀數組更新就是+1,代碼中val可以去掉,直接寫成++,方便讀懂:
void add(int i)
{
while(i<=32000)
{
C[i]++;
i=i+lowbit(i);
}
}
sum()即上文count[0]-[X]求和的代碼實現。
原代碼的寫法可以任意+val,但是如果在其他地方下出現基礎數組A[x]的值從4變成7或者從7變成4呢?
回來看原來的+1,這個+1表示基礎數組中count[X]的值比原來增大了1,變化量為|+1|,而C[X]及父層表示的是一段後綴和,對於求和公式s=a1+a2+a3+...
當一個成員a1=>a*變化了|a|時,原公式要加上|a|,才能使a1=>a*,保證s隨a1變化保持一致。
因此在這個情況下要維護基礎數組A[],add()函數不需要改,只需在調用時:
add(x, tmp-A[x]); //tmp是A[x]新的值
代入4,7自行驗證,所有父層都實現了+3或-3,樹狀數組成功更新。
[題解][樹狀數組] POJ 2352 - Stars 樹狀數組的簡單應用