题目链接POJ3468

题目大意:给你一段长度为n得数字序列,并且有q次操作,这些操作可能是查询[l,r]的数字和或者是修改[l,r]之间的数字。每次做出相应操作,查询则输出结果。

解题思路:关于数字序列区间问题,可以用线段树,但要加个延迟标记(之后更新这种做法)。这里通过树状数组的性质,并且预处理树状数组(差分操作),使其能够区间更新。


关于区间修改的树状数组

  • 这里树状数组我们用c[maxn]表示,数组序列我们用val[maxn]表示。
  • 差分操作:我们设数组tp[i] = val[i] - val[i - 1] (i >= 1, val[0] = 0)
  • 于是有:val[i] = tp[i] + val[i - 1]
  • 迭代一下:val[i - 1] = tp[i - 1] + val[i - 2]
  • 则:val[i] = tp[i] + tp[i - 1] + val[i - 2] = tp[i] + tp[i - 1] + … + tp[1]
  • 上面那个还有最后一项val[0] = 0省略
  • 于是,利用c数组维护tp的前缀和可以用getsum来求得val。
  • 那么,如果是解决区间修改单点查询的问题时,我们到此就完成了。修改操作:update(left, num); update(right, num); 查询操作:getsum(i);
  • 这里不难发现,如果是区间查询时,比如查询[3,6]:其实就是getsum(3) + getsum(4) + getsum(5) + getsum(6)。但如果数据规模一大,每次查询的时候都要做到接近O(n)的加法,体现不了树状数组的优势。观察一下,这个[3,6]的查询操作每次getsum(k)做的是从tp[1]加到tp[k]的和,如果我们定义一个getsum2(k):(tp[1]) + (tp[1] + tp[2]) + (tp[1] + tp[2] + tp[3]) + (tp[1] + … + tp[k])表示从getsum(1) + …getsum (k)的前缀和。式子变形一下:(tp[1] + tp[2] + … + tp[k]) * k - (tp[1] * 0 + tp[2] * 1 + tp[3] * 2 + … + tp[k] * (k - 1) )。 这样一来,[3,6]区间的和就可以通过getsum2(6) - getsum2(3 - 1)求得!但这个形式可能不好处理,我们通过变形式子: k * getsum(k,0) - getsum(k,1)来求得,其中0表示计算tp的前缀和,1表示tp[k] * (k - 1)的前缀和,于是,我们要有两个树状数组!注意可能爆整型!

AC代码

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <string>
#include <deque>
#include <queue>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <utility>

using namespace std;
const int maxn = (int)1e5+5;
typedef long long LL;

LL c[2][maxn];
int n;

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

void add (int idx, LL num, int select) {
	while (idx <= n) {
		c[select][idx] += num;
		idx += lowbit(idx);
	}
}

LL get_sum (int idx, int select) {
	LL ans = 0;
	while (idx > 0) {
		ans += c[select][idx];
		idx -= lowbit(idx);
	}
	return ans;
}

LL query (int s) {  //1-s:区间和
	return s * get_sum(s, 0) - get_sum(s, 1);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);	cout.tie(0);
	LL q,tmp,i,j,k,before;
	string command;
	while (cin >> n >> q) {
		memset(c[0], 0, sizeof(c[0]));
		memset(c[1], 0, sizeof(c[1]));
		before = 0;
		for (i = 1; i <= n; i++) {
			cin >> tmp;
			add(i, tmp - before, 0);
			add(i, (i - 1) * (tmp - before), 1);
			before = tmp;
		}
		while (q--) {
			cin >> command;
			if (command.at(0) == 'Q') {
				cin >> i >> j;
				cout << (query(j) -query(i - 1)) << '\n';
			}else {
				cin >> i >> j >> k;
				add(i, k, 0);
				add(j + 1, -k, 0);
				add(i, (i - 1) * k, 1);
				add(j + 1, j * (-k), 1);
			}
		}
	}
	return 0;
}