区间更新单点查询
树状数组的基本应用是单点更新,区间查询(例如求区间和)。
鉴于树状数组的空间复杂度和时间复杂度都比线段树小 而且代码也短 所以就有大神用强大的脑洞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;
}