1. 程式人生 > >樹狀陣列相關應用之區間更新單點查詢問題

樹狀陣列相關應用之區間更新單點查詢問題

區間更新單點查詢

樹狀陣列的基本應用是單點更新,區間查詢(例如求區間和)。
鑑於樹狀陣列的空間複雜度和時間複雜度都比線段樹小 而且程式碼也短 所以就有大神用強大的腦洞YY出了區間修改+單點查詢的樹狀陣列

  • 區間更新原理
    差分法
    設a陣列表示原始的陣列;

設d[i]=a[i]-a[i-1] (1<i≤n,d[1]=a[1]);

設f[i]=f[i-1]+d[i] (1<i≤n,f[1]=d[1]=a[1]);

設sum[i]=sum[i-1]+f[i] (1<i≤n,sum[1]=f[1]=d[1]=a[1])。
則易知在這裡插入圖片描述

舉個例子,我們求1~3的區間和.


在這裡插入圖片描述

後面的可以依次類推。

那麼,對於一個操作,我們可以讓d[x]加上z,讓d[y+1]減小z,就可以了。

還用剛才的例子。


在這裡插入圖片描述

後面的可以依次類推。

  • 一維樹狀陣列區間更新
    引入差分陣列dif(求差分陣列字首和=查詢單點)
    更新區間[a,b]全體元素+cal:
update_1(dif, si, cal, N);
update_1(dif, ti + 1, -cal, N);
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;
}

HDU-4031:Attack
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
題意
今天是“9·11”恐怖襲擊十週年,基地組織即將再次襲擊美國。然而,這次美國人被一道高牆保護著,這道牆可以看作是一段長度為N.基地組織有一件超級武器,它每秒鐘都能攻擊一堵連續不斷的牆。美國部署了能源盾。每個人都要守住牆的一單位長度。然而,在盾牌防禦一次攻擊後,它需要t秒的時間來冷卻。如果盾牌在第k秒防禦攻擊,它就不能防禦(k+1)第2秒和(k+t-1)第4秒之間的任何攻擊。如果盾牌準備好了,它會在受到攻擊時自動防禦。
在戰爭期間,瞭解自己和敵人的情況是非常重要的。因此,美國指揮官想知道有多少時間,部分長城被成功襲擊。成功的攻擊意味著攻擊不被盾牌保護。
輸入
資料的開始是一個整數T(T≤20),測試用例的數量。
每個測試用例的第一行是三個整數,N, Q, t,牆的長度,攻擊和查詢的數量,以及每個盾牌需要冷卻的時間。
接下來的Q行分別描述了一個攻擊或一個查詢。它可能是下列格式之一
1。攻擊si ti
基地組織從si攻擊到ti,包括在內。1≤si ti≤≤N
2。查詢頁
多少次pth單位被成功攻擊。1 p≤≤N
第k次攻擊發生在第k秒。查詢不需要時間。
1≤N,問≤20000
1≤t≤50
輸出
對於第i種情況,首先輸出一行“case i:”。然後,對於每個查詢,輸出一行包含一個整數,當請求時,第pth單元被成功攻擊的時間。

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

#define MAXSIZE 20005

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

int main()
{
	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 flag;
	int N, Q, t;      //城牆長度,指令數,盾牌冷卻時間
	char order[10];       //指令首字母
	int si, ti;       //攻擊城牆的範圍
	int p;            //查詢的單位城牆
	int p_define;     //防禦成功次數
	int dif[MAXSIZE];    //差分陣列(初始化模擬牆,求差分陣列字首和==>查詢單點被攻擊總次數)
	int left[MAXSIZE];
	int right[MAXSIZE];  //攻擊範圍記錄陣列

	scanf("%d", &flag);
	int i = 1;
	while (i <= flag)
	{
		scanf("%d%d%d", &N, &Q, &t);
		printf("Case %d:\n", i);
		memset(dif, 0, sizeof(dif));
		memset(left, 0, sizeof(left));
		memset(right, 0, sizeof(right));
		int attack = 1;    //攻擊計數器
		//相關陣列及變數初始化完畢
		while (Q--)
		{
			memset(order, '\0', sizeof(order));
			scanf("%s", order);
			if (order[0] == 'A')
			{//攻擊
				scanf("%d%d", &si, &ti);
				update_1(dif, si, 1, N);
				update_1(dif, ti + 1, -1, N);
				//被攻擊範圍攻擊總次數更新完畢
				left[attack] = si;
				right[attack] = ti;
				//攻擊區間記錄完畢
				attack++;
			}
			else
			{//查詢
				p_define = 0;             //成功防禦計數器
				scanf("%d", &p);
				int sum_attack = sum_pre_1(dif, p);      //獲取p處總攻擊次數
				//接下來進行有效防禦次數統計
				int time=1;         //冷卻時間計數器
				while (time <= attack)
				{
					if (p >= left[time] && p <= right[time])       //防禦成功
					{
						time = time + t;                           //跳轉到下一個可防禦時間,在其後進行遍歷
						p_define++;                                //成功防禦次數+1
					}
					else
						time++;
				}
				printf("%d\n", sum_pre_1(dif, p) - p_define);
			}
		}
		i++;
	}
	return 0;
}


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;
}
  • 二維樹狀陣列區間更新
    在這裡插入圖片描述
    我們以深色陰影部分說明:
    (x1,y1)+cal後,求字首和進行單點查詢時DEFG區域的點全+cal,所以進行矩形區間【(x1,y1)–(x2,y2)】更新操作如下:
update_2(arry, x1, y1, cal, N, N);
update_2(arry, x2 + 1, y2 + 1, -cal, N, N);
update_2(arry, x1, y2 + 1, -cal, N, N);
update_2(arry, x2 + 1, y1, cal, N, N);

Poj-2155
在這裡插入圖片描述
在這裡插入圖片描述
題意
給定一個N*N矩陣A,它的元素不是0就是1。A[i, j]表示第i行和第j列中的數字。一開始是A[i, j] = 0 (1 <= i, j <= N)
我們可以這樣改變矩陣。給定一個左上角為(x1, y1)和右下角為(x2, y2)的矩形,我們通過使用“not”操作來更改矩形中的所有元素(如果是“0”,那麼將其更改為“1”,否則將其更改為“0”)。為了維護矩陣的資訊,您需要編寫一個程式來接收和執行兩種指令。
1。C x1 y1 x2 y2 (1 <= x1 <= x2 <= n, 1 <= y1 <= y2 <= n)使用左上角為(x1, y1)、右下角為(x2, y2)的矩形變換矩陣。
2。qxy (1 <= x, y <= n)查詢A[x, y]。
輸入
輸入的第一行是整數X (X <= 10),表示測試用例的數量。下面的X塊分別表示一個測試用例。
每個塊的第一行包含兩個數字N和T (2 <= N <= 1000, 1 <= T <= 50000),表示矩陣的大小和指令的數量。下面的T行分別表示具有“qx y”或“C x1 y1 x2 y2”格式的指令,上面已經描述過了。
輸出
對於每個查詢輸出,有一行表示[x, y]的整數。
每兩個連續的測試用例之間有一個空白行。

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

#define MAXSIZE 1002

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

int arry[MAXSIZE][MAXSIZE];       //樹狀陣列(差分原理)

int main()
{
	int lowbit(int x);
	void update_2(int val[][MAXSIZE], int x, int y, int cal, int arry_num_x, int arry_num_y);
	int sum_pre_2(int val[][MAXSIZE], int x, int y);
	int flag;    //測試用例數
	int N, T;    //矩陣大小,指令數目
	char temp;    //指令型別
	int x, y;
	int num;
	int x1, y1, x2, y2;
	scanf("%d", &flag);
	while (flag--)
	{
		scanf("%d%d", &N, &T);
		memset(arry[0], 0, sizeof(int)*MAXSIZE*MAXSIZE);
		//矩陣初始化完成
		while (T--)
		{
			getchar();    //將換行符拿出來(坑)
			scanf("%c", &temp);
			if (temp == 'C')
			{//區間更新
				scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
				update_2(arry, x1, y1, 1, N, N);
				update_2(arry, x2 + 1, y2 + 1, 1, N, N);
				update_2(arry, x1, y2 + 1, 1, N, N);
				update_2(arry, x2 + 1, y1, 1, N, N);
			}
			else
			{//單點查詢
				scanf("%d%d", &x, &y);
				num = sum_pre_2(arry, x, y);   //求差分陣列字首和查詢單點
				printf("%d\n", num %2);       //判斷0/1
			}
		}
		if (flag>0)        //最後一行不空行
			printf("\n");
	}
	return 0;
}
//二維樹狀陣列
int lowbit(int x)
{//返回二進位制數最低位的1對應的數值
	return x & (-x);      //與運算
}

void update_2(int val[][MAXSIZE], int x, int y, int cal, int arry_num_x, int arry_num_y)
{//當原陣列A[x][y]+cal時,更新樹狀陣列val,arry_num為行和列的最大長度
	for (int i = x;i <= arry_num_x;i += lowbit(i))
		for (int j = y;j <= arry_num_y;j += lowbit(j))
			val[i][j] += cal;
}

int sum_pre_2(int val[][MAXSIZE], int x, int y)
{//求A[x][y]左上方的子矩陣A[1--x][1--y]的和
	int sum = 0;
	for (int i = x;i > 0;i -= lowbit(i))
		for (int j = y;j > 0;j -= lowbit(j))
			sum += val[i][j];
	return sum;
}