【树状数组 线段树 分块】POJ_3468 A Simple Problem with Integers

博客围绕长度为N的序列A的M次操作展开,操作包括区间加值和查询区间和。介绍了三种解决思路,树状数组通过维护特定公式的值来处理;线段树利用延迟标记完成区间修改;分块则将序列分块,根据情况累加答案或标记修改,还给出了对应代码。

题意

给出一个长度为NNN的序列AAA,有MMM次操作,有以下两种:
CCC xxx yyy zzz,把序列从xxxyyy加上zzz
QQQ xxx yyy,查询序列xxxyyy的和。

思路

一、树状数组
我们用bbb的前缀和代表AiA_iAi要增加多少,那么序列AAA的前缀和A[1∼x]A[1 \sim x]A[1x]一共增加的和就为:
∑i=1x∑j=1ib[i]\sum_{i=1}^{x}\sum_{j=1}^{i}b[i]i=1xj=1ib[i]
这个式子可以改成:
(x+1)∑i=1xb[i]−∑i=1xi∗b[i](x+1)\sum_{i=1}^{x}b[i]-\sum_{i=1}^{x}i*b[i](x+1)i=1xb[i]i=1xib[i]
这个公式可以画图理解,详见某蓝书,用树状数组维护一下这两个值就好了。
二、线段树
用一个延迟标记我们就可以完成区间修改的操作,其它的基本操作。
三、分块
把这个序列分成N\sqrt NN块,查找和判断时判断是不是在同一块,在同一块就可以直接累加答案。
如果是一整块的我们就可以直接用addaddadd来标记这一块增加了多少,两边有多余的话我们就可以直接暴力过去查找或修改,细节操作详见代码。

代码

树状数组

#include<cstdio>
#define ll long long
int n, q, a[100001], x, y, z;
ll b_1[100001], b_2[100001], s[100001];
char c;
ll ask(ll b[], int x) {
	ll r = 0;
	for (; x; x -= x & -x) r += b[x];
	return r;
}
void change(ll b[], int x, int v) {
	for (; x <= n; x += x & -x) b[x] += v;
	return;
}
int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		s[i] = s[i - 1] + a[i];
	}
	while (q--) {
		c = getchar();
		while (c != 'C' && c != 'Q') c = getchar();
		if (c == 'Q') {
			scanf("%d %d", &x, &y);
			printf("%lld\n", (s[y] + (y + 1) * ask(b_1, y) - ask(b_2, y)) - (s[x - 1] + x * ask(b_1, x - 1) - ask(b_2, x - 1)));
		}
		else {
			scanf("%d %d %d", &x, &y, &z);
			change(b_1, x, z);
			change(b_1, y + 1, -z);
			change(b_2, x, z * x);
			change(b_2, y + 1, -(y + 1) * z);
		}
	}
	return 0;
}

线段树

#include<cstdio>
#define lson(p) (p) << 1
#define rson(p) lson(p) | 1
struct treenode{
	int l, r;
	long long sum, add;
}t[400001];
int n, q, a[100001], x, y, z;
char c;
void build(int p, int l, int r) {
	t[p].l = l; t[p].r = r;
	if (l == r) {
		t[p].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson(p), l, mid);
	build(rson(p), mid + 1, r);
	t[p].sum = t[lson(p)].sum + t[rson(p)].sum;
	return;
}
void spread(int p) {//下传延迟标记
	if (t[p].add) {
		t[lson(p)].sum += t[p].add * (t[lson(p)].r - t[lson(p)].l + 1);
		t[rson(p)].sum += t[p].add * (t[rson(p)].r - t[rson(p)].l + 1);
		t[lson(p)].add += t[p].add;
		t[rson(p)].add += t[p].add;
		t[p].add = 0;
	}
	return;
}
long long ask(int p, int l, int r) {
	if (l <= t[p].l && t[p].r <= r) return t[p].sum;
	spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	long long result = 0;
	if (l <= mid) result += ask(lson(p), l, r);
	if (r > mid) result += ask(rson(p), l, r);
	return result;
}
void change(int p, int l, int r, int v) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].sum += (long long) v * (t[p].r - t[p].l + 1);
		t[p].add += v;
		return;
	}
	spread(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) change(lson(p), l, r, v);
	if (r > mid) change(rson(p), l, r, v);
	t[p].sum = t[lson(p)].sum + t[rson(p)].sum;
	return;
}
int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	build(1, 1, n);
	while (q--) {
		c = getchar();
		while (c != 'Q' && c != 'C') c = getchar();
		scanf("%d %d", &x, &y);
		if (c == 'Q') printf("%lld\n", ask(1, x, y));
		else {
			scanf("%d", &z);
			change(1, x, y, z);
		}
	}
}

分块

#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
int n, m, t, q, x, y,  pos[100001], l[100001], r[100001];
long long a[100001], z,sum[100001], add[100001];
char c;
void change(int x, int y, long long v) {
	int p = pos[x], q = pos[y];
	if (p == q) {
		for (int i = x; i <= y; i++) a[i] += v, sum[p]+=v;
		return;
	}
	for (int i = p + 1; i < q; i++) add[i] += v;
	for (int i = x; i <= r[p]; i++) a[i] += v, sum[p]+=v;//暴力
	for (int i = l[q]; i <= y; i++) a[i] += v, sum[q]+=v;
}
long long ask(int x, int y) {
	int p = pos[x], q = pos[y];
	long long result = 0;
	if (p == q) {
		for (int i = x; i <= y; i++) result += a[i]+add[p];
		return result;
	}
	for (int i = p + 1; i < q; i++)
		result += sum[i] + add[i] * (long long)(r[i] - l[i] + 1);
	for (int i = x; i <= r[p]; i++) result += a[i] + add[p];//暴力
	for (int i = l[q]; i <= y; i++) result += a[i] + add[q];
	return result;
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	t = sqrt(n);
	for (int i = 1; i <= t; i++) {
		l[i] = (i - 1) * t + 1;//记录每一块的左端点
		r[i] = i * t;//右端点
	}
	if (r[t] < n) l[++t] = r[t - 1] + 1, r[t] = n;//不足再补一块
	for (int i = 1; i <= t; i++)
		for (int j = l[i]; j <= r[i]; j++) {
			pos[j] = i;
			sum[i] += a[j];
		}
	while (m--) {
		while (c = getchar(), c != 'Q' && c != 'C');
		scanf("%d %d", &x, &y);
		if (c == 'C') {
			scanf("%lld", &z);
			change(x, y, z);
		}
		else printf("%lld\n", ask(x, y));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值