1. 程式人生 > >樹狀陣列相關應用之多叉樹子樹問題

樹狀陣列相關應用之多叉樹子樹問題

Poj-3321:Apple Tree
在這裡插入圖片描述
在這裡插入圖片描述
題意:
卡卡的房子外面有一棵蘋果樹。每年秋天,樹上都會長出許多蘋果。卡卡非常喜歡蘋果,所以他一直在精心培育著這棵大蘋果樹。
這棵樹有N個分叉,這些分叉由樹枝相連。卡卡把叉的編號從1到N,根編號總是1。蘋果會在叉子上生長,兩個蘋果不會在同一個叉子上生長。卡卡想知道一個子樹上有多少個蘋果,因為他研究的是蘋果樹的生產能力。
問題是一個新的蘋果可能會在空叉子上生長,卡卡可能會從樹上摘一個蘋果作為甜點。你能幫卡卡嗎?
輸入
第一行包含一個整數N(N≤100000),這是樹中的分支的數量。
下面的N - 1行每一行都包含兩個整數u和v,這意味著fork u和fork v由一個分支連線。
下一行包含一個整數M(≤100000)。
以下M行每一行都包含一個訊息
C x表示叉x上的蘋果已經改變了。比如,如果叉子上有一個蘋果,卡卡就會把它摘下來;否則,空叉子上就會長出一個新的蘋果。

“Q x”表示在叉x上方的子樹中查詢蘋果的數量,包括叉x上的蘋果(如果存在的話)
注意一開始樹裡滿是蘋果
輸出
對於每個查詢,輸出每行對應的答案。
解題思路:


先利用鏈式向前星將樹結構儲存起來,再用dfs獲取dfs序轉成線性結構,之後便可用樹狀陣列通過dfs序對樹進行維護,更改狀態位置為beg【i】,求子樹蘋果數的線性範圍為beg【i】–last【i】。

#include <iostream>
#include <cstring>
#include <stdio.h>

#define MAXSIZE 100005

using namespace std;   //使用名稱空間std

int Head[MAXSIZE], Next[MAXSIZE], To[MAXSIZE];          //鏈式向前星(線性結構存邊)
//Head[i]代表起點為i的邊在向前星中最後儲存的位置(頭指標),To[i]表示向前星第i個位置的邊的終點,Next[i]表示與第i個邊相同起點的下一個邊在向前星中的位置
int beg[MAXSIZE], last[MAXSIZE];                      //begin[i]和last[i]分別儲存以i為起點的連通子圖(樹結構為子樹)的dfs序的起始位置和終止位置
int cnt_edg, cnt_dfs;                                                //向前星下標(0--MAXSIZE),dfs序儲存下標(1--MAXSIZE)(對應樹狀陣列)

bool tree[MAXSIZE];           //蘋果樹蘋果的狀態(0/1)
int val[MAXSIZE];             //樹狀陣列

int main()
{
	void add(int from, int to);
	void dfs(int fa, int son);
	int lowbit(int x);
	void update_1(int val[], int i, int cal, int arry_num);
	int sum_pre_1(int val[], int i);
	int sum_between_1(int val[], int pre, int last);
	int N, M;          //N個分叉,M個指令
	int x1, x2;
	int x;
	char order;        //指令
	while (~scanf("%d", &N)) 
	{
		memset(Head, -1, sizeof(Head));
		memset(val, 0, sizeof(val));
		cnt_edg = 0;
		cnt_dfs = 0;
		for(int i=1;i<N;i++)
		{//加邊
			scanf("%d%d", &x1, &x2);
			add(x1, x2);   
		}
		dfs(-1, 1);    //獲取dfs序
		for (int i = 1;i <= N;i++)
		{
			tree[i] = true;
			update_1(val, i, 1, N);
		}
		//蘋果樹初始化完畢
		scanf("%d", &M);
		while (M--)
		{
			getchar();     //取出緩衝區的換行符
			scanf("%c%d", &order, &x);
			if (order == 'C')
			{//改變x枝的蘋果狀態
				if (tree[x])
				{
					tree[x] = 0;
					update_1(val, beg[x], -1, cnt_dfs);
				}
				else
				{
					tree[x] = 1;
					update_1(val, beg[x], 1, cnt_dfs);
				}
			}
			else
				printf("%d\n", sum_between_1(val, beg[x], last[x]));
		}
	}
	return 0;
}

//鏈式向前星
void add(int from, int to)
{//加邊(from:起點/to:終點)
	To[cnt_edg] = to;               //cnt為向前星下標計數器(第cnt條邊,cnt從0開始)
	Next[cnt_edg] = Head[from];
	Head[from] = cnt_edg++;
}

void dfs(int fa, int son)
{//圖遍歷:fa:父結點,son:子結點   (以son為起點遍歷)  (樹結構是遍歷son對應的子樹)
 //獲得一個以son為起點的dfs序()
	beg[son] = ++cnt_dfs;
	for (int i = Head[son];~i;i = Next[i])             //for迴圈是son的廣度     ~i:i!=-1  (~非運算子,-1的補碼全為1)
		if (fa != To[i])    dfs(son, To[i]);           //廣度的每個子樹對應一個深度
	last[son] = cnt_dfs;       //(單個深度遍歷完畢)
}

//一維樹狀陣列
int lowbit(int x)
{//返回二進位制數最低位的1對應的數值
	return x & (-x);      //與運算
}

//一維樹狀陣列
void update_1(int val[], int i, int cal, int arry_num)
{//原陣列第i個元素加上cal,更新樹狀陣列相關元素,arry_num為原陣列的長度
 //可直接用於樹狀陣列的建立
	for (;i <= arry_num;i += lowbit(i))
		val[i] += cal;
}

int sum_pre_1(int val[], int i)
{//求arry陣列的前i項和
 //val為樹狀陣列地址
	int sum = 0;
	for (;i > 0;i -= lowbit(i))       //從後向前每次跳一個lowbit
		sum += val[i];
	return sum;
}

int sum_between_1(int val[], int pre, int last)
{//求原陣列arry在區間[pre-last]的和
	return sum_pre_1(val, last) - sum_pre_1(val, pre - 1);
}