敵兵佈陣 1166 HDU 線段樹
花了一個下午理解了線段樹的演算法,然後開始做HDU上的1166題。
先說說對於線段樹的一個理解:
比如要在自然數,且所有的數不大於30000的範圍內討論一個問題:現在已知n條線段,把端點依次輸入告訴你,然後有m個(多次)詢問,每個詢問輸入一個點,要求這個點在多少條線段上出現過;
最暴力的解法當然就是讀一個點,就把所有線段比一下,看看在不線上段中;但是每次詢問都要把n條線段查一次,那麼m次詢問,就要運算m*n次,複雜度就是O(m*n)
假如m是30000,那麼計算量達到了10^9;而計算機1秒的計算量大約是10^8的數量級,所以這種方法無論怎麼優化都是超時
因為n條線段是固定的,所以某種程度上說每次都把n條線段查一遍有大量的重複和浪費;
線段樹就是可以解決這類問題的資料結構。
類似於二叉樹,有構造,插入,修改的方法。
關鍵是要設定一個結構體,來存放不同的線段,然後依次往後構造或者修改。其中關鍵是左兒子的標識是父親標識×2,右兒子的標識是父親標識×2+1.還是用HDU的1166來說明下:
題目描述:
第一行一個整數T,表示有T組資料。
每組資料第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裡開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組資料最後出現;
每組資料最多有40000條命令
要求:
對第i組資料,首先輸出“Case i:”和回車,
對於每個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數最多不超過1000000。
首先我們建立一個結構體:
struct
{
int a,b,sum;
}t[140000];
其中a,b我們可以看作是線段的座標,而sum是這個線段上的人數和。
然後是定義線段數的建構函式:
int r[50010],SUM; //r[50010]是存放每個點上的人數,SUM是用來存放查詢的結果。 void make(int x,int y,int num) { t[num].a=x; t[num].b=y; if(x==y) t[num].sum=r[y];//如果x==y,說明已經是葉子節點了,沒有兒子節點了,就顯現成熟單個營地,人數就是r[y] else{ make(x,(x+y)/2,num+num); //構造左兒子樹 make((x+y)/2+1,y,num+num+1); //構造右兒子樹 t[num].sum=t[num+num].sum+t[num+num+1].sum; //父節點的人數等於子結點人數之和,線段被分成兩段。 } }
定義查詢函式:
void query(int x,int y,int num)
{
if(x<=t[num].a&&y>=t[num].b)//找到要求的線段區間,返回其值
SUM+=t[num].sum;
else{
int min=(t[num].a+t[num].b)/2;
if(x>min) query(x,y,num+num+1); //要查詢的線段在該線段右邊,查詢該線段的右子節點
else if(y<=min) query(x,y,num+num);//要查詢的線段在該線段左邊,查詢該線段的左子節點
else{
//要查詢的線段在該線段中間,分段查詢,左右節點都查。
query(x,y,num+num);
query(x,y,num+num+1);
}
}
}
定義新增節點人數的函式:
void add(int x,int y,int num)
{
//從根節點不斷往下更改,只要包含該點x的線段子都增加相應的數量y
t[num].sum+=y;
if(t[num].a==x&&t[num].b==x) return; //找到x的葉子節點。停止。
if(x>(t[num].a+t[num].b)/2) add(x,y,num+num+1);//點x在該線段的右邊,查詢右子節點。
else add(x,y,num+num);//否則查詢左子節點
}
類似的,定義減少節點人數的函式:
void sub(int x,int y,int num)
{
t[num].sum-=y;
if(t[num].a==x&&t[num].b==x) return;
if(x>(t[num].a+t[num].b)/2) sub(x,y,num+num+1);
else sub(x,y,num+num);
}
完整程式碼:
#include<iostream>
using namespace std;
struct
{
int a,b,sum;
}t[140000];
int r[50010],SUM; //r[50010]是存放每個點上的人數,SUM是用來存放查詢的結果。
void make(int x,int y,int num)
{
t[num].a=x;
t[num].b=y;
if(x==y) t[num].sum=r[y];//如果x==y,說明已經是葉子節點了,沒有兒子節點了,就顯現成熟單個營地,人數就是r[y]
else{
make(x,(x+y)/2,num+num); //構造左兒子樹
make((x+y)/2+1,y,num+num+1); //構造右兒子樹
t[num].sum=t[num+num].sum+t[num+num+1].sum; //父節點的人數等於子結點人數之和,線段被分成兩段。
}
}
void query(int x,int y,int num)
{
if(x<=t[num].a&&y>=t[num].b)//找到要求的線段區間,返回其值
SUM+=t[num].sum;
else{
int min=(t[num].a+t[num].b)/2;
if(x>min) query(x,y,num+num+1); //要查詢的線段在該線段右邊,查詢該線段的右子節點
else if(y<=min) query(x,y,num+num);//要查詢的線段在該線段左邊,查詢該線段的左子節點
else{
//要查詢的線段在該線段中間,分段查詢,左右節點都查。
query(x,y,num+num);
query(x,y,num+num+1);
}
}
}
void add(int x,int y,int num)
{
//從根節點不斷往下更改,只要包含該點x的線段子都增加相應的數量y
t[num].sum+=y;
if(t[num].a==x&&t[num].b==x) return; //找到x的葉子節點。停止。
if(x>(t[num].a+t[num].b)/2) add(x,y,num+num+1);//點x在該線段的右邊,查詢右子節點。
else add(x,y,num+num);//否則查詢左子節點
}
void sub(int x,int y,int num)
{
t[num].sum-=y;
if(t[num].a==x&&t[num].b==x) return;
if(x>(t[num].a+t[num].b)/2) sub(x,y,num+num+1);
else sub(x,y,num+num);
}
int main(int argc, char* argv[])
{
int n,t,i,j;
char command[6];
cin>>t;
j=0;
while(t--)
{
int temp,a,b;
cin>>n;
r[0]=0;
for(i=1;i<=n;i++)
cin>>r[i];
make(1,n,1);
cout<<"Case "<<++j<<":"<<endl;
while(cin>>command)
{
if(strcmp(command,"End")==0)
break;
else if(strcmp(command,"Query")==0)
{
cin>>a>>b;
SUM=0;
query(a,b,1);
cout<<SUM<<endl;
}else if(strcmp(command,"Add")==0)
{
cin>>a>>b;
add(a,b,1);
}else if(strcmp(command,"Sub")==0)
{
cin>>a>>b;
sub(a,b,1);
}
}
}
return 0;
}
路還很長,加油加油。。。