1. 程式人生 > >敵兵佈陣 1166 HDU 線段樹

敵兵佈陣 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;
}


路還很長,加油加油。。。