「题解」「数据结构」树状数组2

接着上文说(上文题目图片打太多了,卡的很)
这道题如果还像上一题那样建树状数组,必然超时
我们要发挥自己模仿的天分,使得原封不动地搬下来就可以A了
前面说到跟上题一样建树状数组要超时,那我们倒过来,使得单点访问变为Sum,区间修改变为update,有同学肯定想到了差分数组
没错就是他
我们在区间修改,就相当于修改差分数组的两个值
比如我们给区间[l,r]加上x,就相当于给pl加x,给pr+1减x
而我们单点查询,就相当于求差分数组的前缀和


代码

#include 

const int M = 1e6 + 5;
int a[M], p[M];
long long bit[M];
int n, m;

int lowbit(int x) {
	return x & (- x);
}

void update(int k, int temp) {
	for (int i = k; i <= n; i += lowbit(i)) {
		bit[i] += temp;
	}
}

long long Sum(int k) {
	long long tot = 0;
	for (int i = k; i > 0; i -= lowbit(i)) {
		tot += bit[i];
	}
	return tot;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		p[i] = a[i] - a[i - 1];
		update(i, p[i]);
	}
	for (int i = 1; i <= m; i ++) {
		int order;
		scanf("%d", &order);
		if (order == 1) {
			int l, r, x;
			scanf("%d %d %d", &l, &r, &x);
			update(l, x);
			update(r + 1, - x);
		}
		else {
			int k;
			scanf("%d", &k);
			printf("%lld\n", Sum(k));
		}
	}
	return 0;
}

区间修改,区间查询

题目描述

给定数列 a[1], a[2], \dots, a[n] ,你需要依次进行 q 个操作,操作有两类:

  • 1 l r x:给定 l,r,x ,对于所有 i\in[l,r] ,将 a[i] 加上 x (换言之,将 a[l], a[l+1], \dots, a[r] 分别加上 x );
  • 2 l r:给定 l,r ,求 \sum_{i=l}^ra[i] 的值(换言之,求 a[l]+a[l+1]+\dots+a[r] 的值)。

输入格式

第一行包含 2 个正整数 n,q ,表示数列长度和询问个数。保证 1\le n,q\le 10^6
第二行 n 个整数 a[1],a[2],\dots,a[n] ,表示初始数列。保证 |a[i]|\le 10^6
接下来 q 行,每行一个操作,为以下两种之一:

  • 1 l r x:对于所有 i\in[l,r] ,将 a[i] 加上 x
  • 2 l r:输出 \sum_{i=l}^ra[i] 的值。

保证 1\le l\le r\le n, |x|\le 10^6

输出格式

对于每个 2 l r 操作,输出一行,每行有一个整数,表示所求的结果。

样例

样例输入

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

样例输出

15
34
32
33
50

数据范围与提示

对于所有数据, 1\le n,q\le 10^6, |a[i]|\le 10^6 , 1\le l\le r\le n, |x|\le 10^6

分析

p仍为a的差分数组,那么原数组的前缀和
a1+a2+……+ an
=p1+(p1+p2)+(p1+p2+p3)+……+(p1+p2+……+pn)
=n*p1+(n-1)*p2+(n-2)*p3+……+pn
=n*(p1+p2+p3+……+pn)-(0*p1+1*p2+2*p3+……+(n-1)*pn)
观察减式两边,分别将pi和(i-1)*pi建立两个树状数组BIT1和BIT2,BIT1就是差分数组,区间修改按上一例进行;BIT2的增量就不是x了,而是x*(i-1)。至于区间查询,我们已经知道原数组前缀和了,直接相减即可查询区间和。

代码

#include 
#include 

const int M = 1e6 + 5;
long long a[M], p[M];
long long bit1[M], bit2[M];
int n, m;

long long Read() {				 //读入优化
	long long sym;
	long long tot = 0;
	char x = getchar();
	while ((x > '9' || x < '0') && x != '-') x = getchar();
	if(x == '-') {
		sym = -1;
		x = getchar();
	}
	else
		sym = 1;
	while (x >= '0' && x <= '9') {
		tot = tot * 10 + (x - '0');
		x = getchar();
	}
	return sym * tot;
}

int lowbit(int x) {
	return x & (- x);
}

void update(int k, long long temp) {
	for (int i = k; i <= n; i += lowbit(i)) {
		bit1[i] += temp, bit2[i] += temp * (k - 1);
	}
}

long long Sum1(int k) {
	long long tot = 0;
	for (int i = k; i > 0; i -= lowbit(i)) {
		tot += bit1[i];
	}
	return tot;
}

long long Sum2(int k) {
	long long tot = 0;
	for (int i = k; i > 0; i -= lowbit(i)) {
		tot += bit2[i];
	}
	return tot;
}

int main() {
	n = Read();
	m = Read();
	for (int i = 1; i <= n; i ++) {
		a[i] = Read();
		p[i] = a[i] - a[i - 1];
		update(i, p[i]);
	}
	for (int i = 1; i <= m; i ++) {
		int order = Read();
		if (order == 1) {
			int l = Read(), r = Read();
			long long x = Read();
			update(l, x);
			update(r + 1, - x);
		}
		else {
			int l = Read(), r = Read();
			printf("%lld\n", (Sum1(r) * r - Sum2(r) - (Sum1(l - 1) * (l - 1) - Sum2(l - 1) )));
		}
	}
	return 0;
}

求逆序对数

题目描述

“装满了鹅卵石的瓶子是满的吗?”墨老师曾经这样问过他的学生。“不是,因为还可以放入小石子、再放入细砂、最后再倒入水。”学生们回答。“那么从中可以得到什么启示呢?”墨老师又问,“启示我们时间总是可以挤出来的!”一个聪明的学生抢答。“你说得对!”墨老师微笑道,“但我还要告诉你们另一个重要经验,那就是:如果你不先将大的鹅卵石放进瓶子里去,你也许以后永远没机会再把它们放进去了。”

但这世上的很多人,做事却经常分不清事情的轻重缓急。我们可爱的典狱长大人就犯了这个错误,当他看到身高参差不齐的狱警们排成一列时,眉毛拧成了一个结,他最想知道的就是,到底有多少个狱警逆序排队了。这可以抽象为求逆序对的个数问题:即对于一个包含n个非负整数的数组A[1,…,n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对。

例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2)共4个。

输入格式

输入文件reverse.in包括两行,第一行是一个整数n(1≤n≤1000),表示狱警人数。第二行包含n个整数,用空格分隔,即每个狱警的身高,狱警身高均在int范围内。

输出格式

输出文件reverse.out包括一行,这一行只包含一个整数,即逆序对的个数。

样例

样例输入

5
3 1 4 5 2

样例输出

4

欲知后事如何,且听下回分解

你可能感兴趣的:(数据结构,数据结构,c++)