1. 程式人生 > >#174-【線段樹】忠誠

#174-【線段樹】忠誠

Description

老管家是一個聰明能幹的人。他為財主工作了整整10年,財主為了讓自已賬目更加清楚。要求管家每天記k次賬,由於管家聰明能幹,因而管家總是讓財主十分滿意。但是由於一些人的挑撥,財主還是對管家產生了懷疑。於是他決定用一種特別的方法來判斷管家的忠誠,他把每次的賬目按1,2,3…編號,然後不定時的問管家問題,問題是這樣的:在a到b號賬中最少的一筆是多少?為了讓管家沒時間作假他總是一次問多個問題。 

     在詢問過程中賬本的內容可能會被修改 

Input

輸入中第一行有兩個數m,n表示有m(m<=100000)筆賬,n表示有n個問題,n<=100000。 

第二行為m個數,分別是賬目的錢數. 

接下來每行為3個數字,第一個p為數字1或數字2,第二個數為x,第三個數為y 

當p=1 則查詢x,y區間 

當p=2 則改變第x個數為y 

Output

輸出檔案中為每個問題的答案。具體檢視樣例。 

Sample Input

10 3
1 2 3 4 5 6 7 8 9 10
1 2 7
2 2 0
1 1 10

Sample Output

2 0

可以說是線段樹模板題之一.

#include <iostream> // scanf(), printf() 等各種函式都需要 (Dev-C++ 可以, 但是洛谷提交一定要加上一句 : #include <cstdio> !!!)

#define SIZE 400010 // 陣列大小,線上段樹問題中一般是資料個數的四倍
#define INF 2e+09 // 用一個很大的數 (如 2e+09, 或2 000 000 000) 來表示無窮大

using namespace std;

int res[SIZE]; // 儲存區間的最小值,下標是對應節點的代號

void buildtree(int pos, int l, int r) // 建一顆線段樹
// pos : 當前節點的代號
// l : 當前區間的左端
// r : 當前區間的右端
// 該函式的時間複雜度 : O (n log2 n)
{
	int mid;
	
	if (l == r) // 此區間只有一個數!
	{
		scanf("%d", &res[pos]); // 直接把這個資料給輸入了
		return; // 直接溜走
	}
	mid = l + r >> 1; // 得到區間的中間點mid
	buildtree(pos << 1, l, mid); // 建造表示該區間左半段的樹
	buildtree((pos << 1) + 1, mid + 1, r); // 建造表示該區間右半段的樹
	res[pos] = min(res[pos<<1], res[(pos<<1)+1]); // 當前區間最小值取該區間左半段和該區間右半段最小值的最小值
	
	return;
}

void update(int pos, int l, int r, int x, int y) // 更新一個點的值
// pos : 當前節點的代號
// l : 當前區間的左端
// r : 當前區間的右端
// x : 要改變的數的下標
// y : 要把那個數改為y
// 該函式的時間複雜度 : O (log2 n)
{
	int mid;
	
	if (l == r) // 該區間只有一個數,也就是說找到了要改變的那個數!
	{
		res[pos] = y; // 直接改變該數的值
		return; // 直接溜了
	}
	mid = l + r >> 1; // mid表示該區間的中間點
	if (x <= mid) // 如果要改變的點在該區間的左半段
	{
		update(pos << 1, l, mid, x, y); // 那麼,就在該區間的左半段尋找要改變的數
	}
	else // 否則,要改變的點在該區間的右半段
	{
		update((pos << 1) + 1, mid + 1, r, x, y); // 那麼,就在該區間的右半段尋找要改變的數
	}
	res[pos] = min(res[pos<<1], res[(pos<<1)+1]); // 由於這個區間的左半段或者右半段的最小值已經被改變,需要更新該區間的最小值
	
	return;
}

int query(int pos, int l, int r, int x, int y) // 詢問區間 [x, y] 中的最小值
// pos : 當前節點的代號
// l : 當前區間的左端
// r : 當前區間的右端
// x : 要查詢最小值的區間的左端
// y : 要查詢最小值的區間的右端
// 該函式時間複雜度 : O (log2 n)
{
	int t1, t2, mid;
	
	if ((r < x) || (l > y)) // 此區間和要詢問的最小值的區間 [x, y] 沒有重合部分
	{
		return INF; // 直接開溜
	}
	if ((x <= l) && (y >= r)) // 此區間完全包含在要詢問最小值的區間 [x, y] 中
	{
		return res[pos]; // 直接返回
	}
	mid = l + r >> 1; //  mid表示該區間的中間點
	t1 = t2 = INF; // 這樣做是為了不符合下一步遞迴條件的值設為無窮大 (INF)
	if (x <= mid) // 如果該區間的左半段 [l, mid] 和要詢問最小值的區間 [x, y] 有重合部分
	{
		t1 = query(pos << 1, l, mid, x, y); // 在該區間的左半段 [l, mid] 尋找答案
	}
	if (y > mid) // 如果該區間的右半段 [mid + 1, r] 和要詢問最小值的區間 [x, y] 有重合部分
	{
		t2 = query((pos << 1) + 1, mid + 1, r, x, y); // 在該區間的右半段 [mid + 1, r] 尋找答案
	}
	
	return min(t1, t2); // 返回該區間的左半段的答案和右半段的答案的最小值
}

int main(void) // 終於到主函數了!
{
	int n, m, p, x, y;
	
	scanf("%d%d", &n, &m); // 輸入資料數目以及操作個數
	buildtree(1, 1, n); // 建一顆線段樹,順便輸入資料
	while (m--)
	{
		scanf("%d%d%d", &p, &x, &y); // 輸入操作編號和操作引數
		if (p == 1)
		{
			printf("%d ", query(1, 1, n, x, y)); // 詢問區間的最小值
		}
		else
		{
			update(1, 1, n, x, y); // 更新一個數據
		}
	}
	
	return 0;
}