樹狀陣列相關應用之區間更新單點查詢問題
區間更新單點查詢
樹狀陣列的基本應用是單點更新,區間查詢(例如求區間和)。
鑑於樹狀陣列的空間複雜度和時間複雜度都比線段樹小 而且程式碼也短 所以就有大神用強大的腦洞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;
}