树状数组相关应用之区间更新单点查询问题

区间更新单点查询

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

还用刚才的例子。


在这里插入图片描述

后面的可以依次类推。

扫描二维码关注公众号,回复: 4394383 查看本文章
  • 一维树状数组区间更新
    引入差分数组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;
}

猜你喜欢

转载自blog.csdn.net/qq_40432881/article/details/83350526