线段树,ACM暑期培训
例题:245. 你能回答这些问题吗
给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
1 x y
,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤ymax�≤�≤�≤�{∑i=lrA[i]∑�=���[�]}。2 x y
,把 A[x]改成 y。
对于每个查询指令,输出一个整数表示答案。
输入格式
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行每行 33 个整数 k,x,y,k=1 表示查询(此时如果 x>y,请交换 x,y),k=2 表示修改。
输出格式
对于每个查询指令输出一个整数表示答案。
每个答案占一行。
数据范围
N≤500000,M≤100000
−1000≤A[i]≤1000
输入样例:
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1
解析:单点修改,区间查询
(树状数组也能单点修改,区间查询,但无法查最大值,树状数组相当于升级版前缀和)
由于左右子节点互相独立,因此向父节点转移的 tmax
无非是以下三种情况:
1.左子节点的最大连续子段和 l.dat
2.右子节点的最大连续子段和 r.dat
3.左子节点的最大后缀和 {l.rmax} + 右子节点的最大前缀和 {r.lmax}。
即
t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
t[p].sum = t[l].sum + t[r].sum;
所以代码为:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 5e5 + 5;
struct node {
int l, r;
int dat, sum, lmax, rmax;
}t[N * 4];
int arr[N];
int n, m;
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
t[p].dat = arr[l];
t[p].sum = arr[l];
t[p].lmax = arr[l];
t[p].rmax = arr[l];
return;
}
int mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
l = p * 2, r = p * 2 + 1;
t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
t[p].sum = t[l].sum + t[r].sum;
}
void fun(int p) {
int l = p * 2, r = p * 2 + 1;
t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
t[p].sum = t[l].sum + t[r].sum;
}
void change(int p, int x, int v) {
if (t[p].l == t[p].r) {
t[p].dat = v;
t[p].sum = v;
t[p].lmax = v;
t[p].rmax = v;
return;
}
int mid = (t[p].l + t[p].r) / 2;
if (x <= mid) {
change(p * 2, x, v);
}
else {
change(p * 2 + 1, x, v);
}
fun(p);
}
void fun2(node& ret, node& l, node& r) {
ret.dat = max(max(l.dat, r.dat), l.rmax + r.lmax);
ret.rmax = max(r.rmax, l.rmax + r.sum);
ret.lmax = max(l.lmax, l.sum + r.lmax);
r.sum = l.sum + r.sum;
}
node ask(int p, int l, int r) {
if (l <= t[p].l && r >= t[p].r)return t[p];
int mid = (t[p].l + t[p].r) / 2;
if (r <= mid)return ask(p * 2, l, r);
else if (l > mid)return ask(p * 2 + 1, l, r);
else {
node left = ask(p * 2, l, r);
node right = ask(p * 2 + 1, l, r);
node ret;
fun2(ret, left, right);
return ret;
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
}
build(1, 1, n);
int temp, x, y;
while (m--) {
scanf("%d%d%d", &temp, &x, &y);
if (temp == 1) {
if (x > y) swap(x, y);
cout << ask(1, x, y).dat << endl;
}
else if (temp == 2) {
change(1, x, y);
}
}
return 0;
}
区间和-线段树
Problem:B
Time Limit:1000ms
Memory Limit:65535K
Description
给定一数列,规定有两种操作,一是修改某个元素,二是求区间的连续和。
Input
输入数据第一行包含两个正整数n,m(n<=100000,m<=500000),以下是m行, 每行有三个正整数k,a,b(k=0或1, a,b<=n).k=0时表示将a处数字加上b,k=1时表示询问区间[a,b]内所有数的和。
Output
对于每个询问输出对应的答案。
Sample Input
10 20 0 1 10 1 1 4 0 6 6 1 4 10 1 8 9 1 4 9 0 10 2 1 1 8 0 2 10 1 3 9 0 7 8 0 3 10 0 1 1 1 3 8 1 6 9 0 5 5 1 1 8 0 4 2 1 2 8 0 1 1
Sample Output
10 6 0 6 16 6 24 14 50 41
Hint
Source
信息学奥赛一本通
解析:单点修改,区间查询
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct node {
int l, r;
LL dat;
}t[N * 4];
int arr[N];
int n, m;
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
t[p].dat = arr[l];
return;
}
int mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
l = p * 2, r = p * 2 + 1;
t[p].dat = t[l].dat + t[r].dat;
}
void change(int p, int x, int v) {
if (t[p].l == t[p].r) {
t[p].dat += v;
return;
}
int mid = (t[p].l + t[p].r) / 2;
if (x <= mid) {
change(p * 2, x, v);
}
else {
change(p * 2 + 1, x, v);
}
int l = p * 2, r = p * 2 + 1;
t[p].dat = t[l].dat + t[r].dat;
}
void fun2(node& ret, node& l, node& r) {
ret.dat = l.dat + r.dat;
}
node ask(int p, int l, int r) {
if (l <= t[p].l && r >= t[p].r)return t[p];
int mid = (t[p].l + t[p].r) / 2;
if (r <= mid)return ask(p * 2, l, r);
else if (l > mid)return ask(p * 2 + 1, l, r);
else {
node left = ask(p * 2, l, r);
node right = ask(p * 2 + 1, l, r);
node ret;
fun2(ret, left, right);
return ret;
}
}
int main() {
cin >> n >> m;
build(1, 1, n);
int temp, x, y;
while (m--) {
scanf("%d%d%d", &temp, &x, &y);
if (temp == 1) {
if (x > y) swap(x, y);
printf("%lld\n",ask(1,x,y).dat);
}
else {
change(1, x, y);
}
}
return 0;
}
快乐的雨季---线段树
Problem:A
Time Limit:5000ms
Memory Limit:65535K
Description
六月到来,长江流域进入了雨季,在长江流域有一个小镇,这个小镇上的百姓都住在一条直线上,共有n户人家,编号为1~n,在直线上按编号依次坐落。进入雨季来,这个小镇共下了q次雨,每次下雨覆盖范围是一个连续的区间(L,R),表示编号为L至R的家庭位于降雨区,降雨量为x。镇长非常关心雨后受灾问题,于是每场雨后他想知道该场雨降雨区的家庭自进入雨季以来总的降水量,你能帮帮他吗?
Input
多组数据输入。 每组数据的第一行n,q(1<=n,q<=1e5)表示该小镇共有n户人家,共下了q场雨。 接下来q行,每行3个整数L,R,x (1<=L<=R<=n,1<=x<=10000)表示该场雨覆盖范围和降雨量。
Output
每场雨后输出降雨区总的降水量(自进入雨季来总的降水量,包括该场雨)。
Sample Input
6 4 2 5 10 3 6 7 1 6 102 4 5 57
Sample Output
40 58 680 352
Hint
Source
CJJ
解析:区间修改,区间查询,懒标记
详见《算法竞赛进阶指南》238;
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
int l, r;
LL sum, add;
}tree[4*N];
int arr[N], n, m;
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) {
tree[p].sum = arr[l];
return;
}
int mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
tree[p].sum = tree[2 * p].sum + tree[2 * p + 1].sum;
}
void spread(int p) {
if (tree[p].add) {
tree[2 * p].sum += tree[p].add * (tree[2 * p].r - tree[2 * p].l + 1);
tree[2 * p+1].sum += tree[p].add * (tree[2 * p+1].r - tree[2 * p+1].l + 1);
tree[2 * p].add += tree[p].add;
tree[2 * p+1].add += tree[p].add;
tree[p].add = 0;
}
}
void change(int p, int l, int r, int d) {
if (l <= tree[p].l && r >= tree[p].r) {
tree[p].sum += (long long)d * (tree[p].r - tree[p].l + 1);
tree[p].add += d;
return;
}
spread(p);
int mid = (tree[p].l + tree[p].r) / 2;
if (l <= mid)change(2 * p, l, r, d);
if (r > mid)change(2 * p + 1, l, r, d);
tree[p].sum = tree[2 * p].sum + tree[p * 2 + 1].sum;
}
LL ask(int p, int l, int r) {
if (l <= tree[p].l && r >= tree[p].r)
return tree[p].sum;
spread(p);
int mid = (tree[p].l + tree[p].r) / 2;
LL val = 0;
if (l <= mid)val += ask(p * 2, l, r);
if (r > mid)val += ask(p * 2 + 1, l, r);
return val;
}
int main() {
/*cin >> n >> m;*/
/*for (int i = 1; i <= n; i++) {
scanf("%d", arr[i]);
}*/
while (scanf("%d%d",&n,&m)!=EOF) {
memset(arr, 0, sizeof(arr));
memset(tree, 0, sizeof(tree));
build(1, 1, n);
while (m--) {
int l, r, d;
scanf("%d%d%d", &l, &r, &d);
change(1, l, r, d);
printf("%lld\n", ask(1, l, r));
}
}
return 0;
}
A Simple Problem with Integers-线段树
Problem:C
Time Limit:1000ms
Memory Limit:65535K
Description
You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval. 一个长度为n的序列,m次询问。区间所有数加上一个值,或者询问一段区间的和。n,m<=1e5
Input
he first line contains two numbers N and M. 1 ≤ N,M ≤ 100000. The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000. Each of the next M lines represents an operation. "C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000. "Q a b" means querying the sum of Aa, Aa+1, ... , Ab.
Output
输出查询结果。
Sample Input
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4
Sample Output
4 55 9 15
Hint
区间加上一个值,int 能搞定吗?
Source
POJ Monthly--2007.11.25, Yang Yi
解析:区间修改,区间查询,懒标记
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
int l, r;
LL sum, add;
}tree[4 * N];
int arr[N], n, m;
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) {
tree[p].sum = arr[l];
return;
}
int mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
tree[p].sum = tree[2 * p].sum + tree[2 * p + 1].sum;
}
void spread(int p) {
if (tree[p].add) {
tree[2 * p].sum += tree[p].add * (tree[2 * p].r - tree[2 * p].l + 1);
tree[2 * p + 1].sum += tree[p].add * (tree[2 * p + 1].r - tree[2 * p + 1].l + 1);
tree[2 * p].add += tree[p].add;
tree[2 * p + 1].add += tree[p].add;
tree[p].add = 0;
}
}
void change(int p, int l, int r, int d) {
if (l <= tree[p].l && r >= tree[p].r) {
tree[p].sum += (long long)d * (tree[p].r - tree[p].l + 1);
tree[p].add += d;
return;
}
spread(p);
int mid = (tree[p].l + tree[p].r) / 2;
if (l <= mid)change(2 * p, l, r, d);
if (r > mid)change(2 * p + 1, l, r, d);
tree[p].sum = tree[2 * p].sum + tree[p * 2 + 1].sum;
}
LL ask(int p, int l, int r) {
if (l <= tree[p].l && r >= tree[p].r)
return tree[p].sum;
spread(p);
int mid = (tree[p].l + tree[p].r) / 2;
LL val = 0;
if (l <= mid)val += ask(p * 2, l, r);
if (r > mid)val += ask(p * 2 + 1, l, r);
return val;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
}
build(1, 1, n);
while (m--) {
char op[2];
int l, r, d;
scanf("%s%d%d", &op, &l, &r);
if (op[0] == 'C') {
scanf("%d", &d);
change(1, l, r, d);
}
else
printf("%lld\n", ask(1, l, r));
}
return 0;
}
校门外的树3-线段树
Problem:F
Time Limit:1000ms
Memory Limit:65535K
Description
校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的…… 如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作: K=1,K=1,读入l、r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同 K=2,读入l,r表示询问l~r之间能见到多少种树 (l,r>0)
Input
第一行n,m表示道路总长为n,共有m个操作 接下来m行为m个操作
Output
对于每个k=2输出一个答案
Sample Input
5 4 1 1 3 2 2 5 1 2 4 2 3 5
Sample Output
1 2
Hint
范围:20%的数据保证,n,m<=100 60%的数据保证,n <=1000,m<=50000 100%的数据保证,n,m<=50000
Source
vijos dejiyu@CSC WorkGroup
解析:不会
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
LL c[N], c1[N];
void add(int x, int d, LL* arr) {
for (; x <= n; x += x & -x) {
arr[x] += d;
}
}
LL sum(int x, LL* arr) {
LL ans = 0;
for (; x; x -= x & -x) {
ans += arr[x];
}return ans;
}
int main() {
cin >> n >> m;
int op,l, r;
int mx = 0;
while (m--) {
scanf("%d%d%d", &op, &l, &r);
if (op == 1) {
add(r, 1, c);
add(l, 1, c1);
mx++;
}
else {
printf("%lld\n", mx - sum(l - 1, c) - (sum(n, c1) - sum(r, c1)));
}
}
return 0;
}
P2023 [AHOI2009] 维护序列
P2023 [AHOI2009] 维护序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
题目描述
有一个长为 n 的数列 {an},有如下三种操作形式:
- 格式
1 t g c
,表示把所有满足t≤i≤g 的ai 改为ai×c ; - 格式
2 t g c
表示把所有满足 t≤i≤g 的ai 改为 ai+c ; - 格式
3 t g
询问所有满足t≤i≤g 的 ai 的和,由于答案可能很大,你只需输出这个数模 p 的值。
输入格式
第一行两个整数 n 和 p。
第二行含有 n 个非负整数,表示数列 {ai} 。
第三行有一个整数 m,表示操作总数。
从第四行开始每行描述一个操作,同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
输出格式
对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
输入输出样例
输入 #1复制
7 43 1 2 3 4 5 6 7 5 1 2 5 5 3 2 4 2 3 7 9 3 1 3 3 4 7
输出 #1复制
2 35 8
说明/提示
样例输入输出 1 解释
- 初始时数列为 {1,2,3,4,5,6,7}{1,2,3,4,5,6,7}。
- 经过第 11 次操作后,数列为 {1,10,15,20,25,6,7}{1,10,15,20,25,6,7}。
- 对第 22 次操作,和为 10+15+20=4510+15+20=45,模 4343 的结果是 22。
- 经过第 33 次操作后,数列为 {1,10,24,29,34,15,16}{1,10,24,29,34,15,16}。
- 对第 44 次操作,和为 1+10+24=351+10+24=35,模 4343 的结果是 3535。
- 对第 55 次操作,和为 29+34+15+16=9429+34+15+16=94,模 4343 的结果是88。
数据规模与约定
测试数据规模如下表所示:
数据点编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9,10 |
---|---|---|---|---|---|---|---|---|---|
n= | 10 | 1000 | 1000 | 10000 | 60000 | 70000 | 80000 | 90000 | 100000 |
m= | 10 | 1000 | 1000 | 10000 | 60000 | 70000 | 80000 | 90000 | 100000 |
对于全部的测试点,保证0≤p,ai,c≤109,1≤t≤g≤n。
解析:区间修改,区间查询,双重懒标记(先乘后加)
本题不同于前一道懒标记的题,本题有加法又有乘法,而且不同的转移顺序最终的结果会不一样
举个例子:比如现在有3个数1,2,3
t[1].add+=2;
t[1].sum+=((3−1)+1)∗2;
再给1~3乘上3
所以
t[1].mu∗=3;
再给1~3加上4,那是不是先加再乘
t[1].add+=4;
显然:
操作2之后的式子是:
sum=(a[1]+2)*3+(a[2]+2)*3+(a[3]+2)*3;
如果直接加
式子是:
sum=(a[1]+2+4)*3+(a[2]+2+4)*3+(a[3]+2+4)*3;
=(a[1]+2)*3+4*3+(a[2]+2)*3+4*3+(a[3]+2)*3+4*3;
发现这和
sum=(a[1]+2)*3+4+(a[2]+2)*3+4+(a[3]+2)*3+4;
并不等价
而要等价必须这样
sum=(a[1]+2+4/3)*3+(a[2]+2+4/3)*3+(a[3]+2+4/3)*3;
所以最后应该这么写
sum=(a[1]*3+2*3+4)+(a[2]*3+2*3+4)+(a[3]*3+2*3+4);
注:此题解参考自:题解 P3373 【【模板】线段树 2】 - milkfilling 的博客 - 洛谷博客 (luogu.com.cn)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
int l, r;
LL sum, add,mu;
}tree[4 * N];
int arr[N], n, m;
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r, tree[p].mu = 1, tree[p].add = 0;
if (l == r) {
tree[p].sum = arr[l];
return;
}
LL mid = (l + r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
tree[p].sum = (tree[2 * p].sum + tree[2 * p + 1].sum)%m;
}
//void spread(int p) {
// if (tree[p].add || tree[p].mu != 1) {
// tree[2 * p].sum *= tree[p].mu;
// tree[p * 2].sum %= m;
// tree[p * 2].sum += (LL)(tree[p].add % m * (tree[p * 2].r - tree[p * 2].l + 1) % m) % m;
// tree[p * 2].sum %= m;
// tree[p * 2].add = (tree[p * 2].add * (tree[p].mu % m)) % m;
// tree[p * 2].add += tree[p].add % m;
// tree[p * 2].add %= m;
// tree[p * 2].mu *= tree[p].mu;
// tree[p * 2].mu %= m;
//
// tree[2 * p+1].sum *= tree[p].mu;
// tree[p * 2+1].sum %= m;
// tree[p * 2+1].sum += (LL)(tree[p].add % m * (tree[p * 2+1].r - tree[p * 2+1].l + 1) % m) % m;
// tree[p * 2+1].sum %= m;
// tree[p * 2 + 1].add = (tree[p * 2+1].add * tree[p].mu ) % m;
// tree[p * 2+1].add += tree[p].add % m;
// tree[p * 2+1].add %= m;
// tree[p * 2+1].mu *= tree[p].mu;
// tree[p * 2+1].mu %= m;
//
// tree[p].add = 0;
// tree[p].mu = 1;
// }
//}
void spread(LL p)
{
if (tree[p].add || tree[p].mu != 1) {
tree[p * 2].sum = (tree[p * 2].sum * tree[p].mu) % m;
tree[p * 2 + 1].sum = (tree[p * 2 + 1].sum * tree[p].mu) % m;
tree[p * 2].sum = (tree[p * 2].sum + tree[p].add * (tree[p * 2].r - tree[p * 2].l + 1)) % m;
tree[p * 2+1].sum = (tree[p * 2+1].sum + tree[p].add * (tree[p * 2+1].r - tree[p * 2+1].l + 1)) % m;
tree[p * 2].mu = (tree[p * 2].mu * tree[p].mu) % m;
tree[p * 2+1].mu = (tree[p * 2+1].mu * tree[p].mu) % m;
tree[p * 2].add = (tree[p * 2].add * tree[p].mu) % m;
tree[p * 2+1].add = (tree[p * 2+1].add * tree[p].mu) % m;
tree[p * 2].add = (tree[p * 2].add + tree[p].add) % m;
tree[p * 2+1].add = (tree[p * 2+1].add + tree[p].add) % m;
tree[p].add = 0;
tree[p].mu = 1;
}
}
void cheng(int p, int l, int r, int d) {
if (l <= tree[p].l && r >= tree[p].r) {
d %= m;
tree[p].sum *= (LL)(d);
tree[p].mu *= (LL)(d);
tree[p].add *= (LL)(d);
tree[p].sum %= m;
tree[p].mu %= m;
tree[p].add %= m;
return;
}
spread(p);
LL mid = (tree[p].l + tree[p].r) / 2;
if (l <= mid)cheng(p * 2, l, r, d);
if (r > mid)cheng(p * 2 + 1, l, r, d);
tree[p].sum = (tree[p * 2].sum + tree[p * 2 + 1].sum) % m;
}
void jia(int p, int l, int r, int d) {
if (l <= tree[p].l && r >= tree[p].r) {
d %= m;
tree[p].sum = (LL)((tree[p].sum + (d * (tree[p].r - tree[p].l + 1) % m) % m) % m);
tree[p].add = (LL)((tree[p].add + d) % m);
return;
}
spread(p);
LL mid = (tree[p].r + tree[p].l) / 2;
if (l <= mid)jia(p * 2, l, r, d);
if (r > mid)jia(p * 2 + 1, l, r, d);
tree[p].sum = (tree[p * 2].sum + tree[p * 2 + 1].sum) % m;
}
LL ask(int p, int l, int r) {
if (l <= tree[p].l && r >= tree[p].r)
return tree[p].sum;
spread(p);
LL mid = (tree[p].l + tree[p].r) / 2;
LL val = 0;
if (l <= mid)val += ask(p * 2, l, r);
if (r > mid)val += ask(p * 2 + 1, l, r);
return val%m;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
}
build(1, 1, n);
int num;
cin >> num;
while (num--) {
int op;
int l, r, d;
scanf("%d%d%d", &op, &l, &r);
if (op == 1) {
scanf("%d", &d);
cheng(1,l,r,d);
}
else if (op == 2) {
scanf("%d", &d);
jia(1,l,r,d);
}
else {
printf("%lld\n", ask(1, l, r));
}
}
return 0;
}
花神游历各国-线段树
Description
花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线型的,也就是说,所有游历国家呈一条线的形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。 每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度由t 变为sqrt(t), (可能是花神虐爆了那些国家的 OI,从而感到乏味)。现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。
Input
一行是一个整数 N,表示有 N 个国家; 第二行有 N 个空格隔开的整数,表示每个国家的初始喜欢度ti 第三行是一个整数 M,表示有 M 条信息要处理; 第四行到最后,每行三个整数 x,l,r,当 x=1 时询问游历国家 l 到 r 的开心值总和,当 x=2 时国家 l 到 r 中每个国家的喜欢度由ti变成sqrt(ti);
Output
每次 x=1 时,每行一个整数。表示这次旅行的开心度。
Sample Input
4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4
Sample Output
101 11 11
Hint
建议使用 sqrt 函数,且向下取整。n,m<=2e5,1<=l<=r<=n;0<=ti<=1e9,注意:开始时的ti可以取0,0不允许开平方的,要标记为不用处理!
Source
BZOJ 3211, loj10128,奥赛一本通
解析:
这道题自己没想出来
由于这个1e8的数字最多只能开根号6次,复杂度最高n*nlog(n);
没跑满,能过
直接进行开根号到叶子节点,然后不断更新。复杂度并不高,但是注意的是,如果当前节点已经是1或者0那么就不要处理了,在更新的过程中多加一个判断修改一下这个题目就可以AC了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
struct tree {
LL l, r;
LL sum, flag;
}t[4*N];
int n, m;
int arr[N];
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (t[p].l == t[p].r) {
t[p].sum = arr[l];
t[p].flag = arr[l] <= 1 ? 0 : 1;
return;
}
LL mid = (l+r) / 2;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1,r);
t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
t[p].flag = max(t[p * 2].flag, t[p * 2 + 1].flag);
}
void change(int p, int l, int r) {
if (t[p].flag == 0)return;
if (t[p].l == t[p].r) {
if (t[p].flag) {
t[p].sum = sqrt(t[p].sum);
t[p].flag = t[p].sum <= 1 ? 0 : 1;
}
return;
}
LL mid = (t[p].l + t[p].r) / 2;
if (l <= mid)change(p * 2, l, r);
if (r > mid)change(p * 2 + 1, l, r);
t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
t[p].flag = max(t[p * 2].flag, t[p * 2 + 1].flag);
}
LL ask(int p, int l, int r) {
if (l<=t[p].l&&r>=t[p].r) {
return t[p].sum;
}
LL mid = (t[p].l + t[p].r) / 2;
LL ret = 0;
if (l <= mid) ret += ask(p * 2, l, r);
if (r > mid)ret+=ask(p * 2 + 1, l, r);
return ret;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
}
build(1, 1,n);
scanf("%d", &m);
int op,l,r;
while (m--) {
scanf("%d%d%d", &op, & l, &r);
if (op == 2) {
change(1, l, r);
}
else {
printf("%lld\n", ask(1, l, r));
}
}
return 0;
}
区间求和-线段树
Problem:H
Time Limit:2000ms
Memory Limit:165535K
Description
有一个序列包含n个正整数,现在有m次询问,每次询问为:求(L,R)的区间中小于等于K的数的和?
Input
输入包含多组数据。每组数据,第一行为n,表示这个整数序列长为n(1<=n<=1e5)。第二行输入整数序列x1,x2,……,xn(1<=xi<=1e9)。第三行输入m(1<=m<=1e5)。接下来m行,每行输入L,R,K(1<=L<=R<=n,0<=K<=1e9)。
Output
每组数据首先输出“Case #x:”,x从1开始,然后m行输出每次询问的结果,具体格式请参考样例。
Sample Input
6 2 5 3 4 7 6 3 2 5 4 3 6 5 1 4 3
Sample Output
Case #1: 7 7 5
Hint
Source
CJJ
解析:单点修改,区间查询,离线处理
将题目所给的n个数和m个数字k全部存入数组arr中:
1.存n个数时还要将n个数的为值w存入,op==1代表当前信息是n个数
2.存入m个k时也要讲对应的信息存入,op==0代表当前信息为k
w带表第几个k,同相较于n个数,这k个数还要将k测量的范围l,r存入
然后arr按照v从小到大进行排序
接着在op==1时对线段树进行单点修改
这样可以保证在访问到k时线段树中的所有叶子节点的值均小于k,所以只要选择对应的区间范围内的sum就行了
详细解析请查看马家沟老三的题解:Problem - 1328 (nefu.edu.cn)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, m;
struct st {
int l, r, k,num;
}arr[4*N];
struct st1 {
int l,r;
LL sum;
}tr[N*4];
typedef struct st2 {
LL v, num;
}st2;
int cmp(const struct st& a, const struct st& b) {
if (a.k == b.k)
return a.num < b.num;
return a.k < b.k;
}
void build(int p, int l, int r) {
tr[p].l = l, tr[p].r = r;
if (l == r) {
return;
}
LL mid = (l + r) / 2;
if (l <= mid)
build(p * 2, l, mid);
if (r > mid)
build(p * 2 + 1, mid + 1, r);
}
void change(int p, int y, int x) {
if (tr[p].l == tr[p].r) {
tr[p].sum = (LL)x;
return;
}
LL mid = (tr[p].l + tr[p].r) / 2;
if (y <= mid)change(p * 2, y, x);
if (y > mid)change(p * 2 + 1, y, x);
tr[p].sum = tr[p * 2].sum + tr[p * 2 + 1].sum;
}
LL ask(int p, int l, int r) {
if (l <= tr[p].l && r >= tr[p].r) {
return tr[p].sum;
}
LL mid = (tr[p].l + tr[p].r) / 2;
LL val = 0;
if (l <= mid)val+=ask(p * 2, l, r);
if (r > mid)val += ask(p * 2 + 1, l, r);
return val;
}
int cmp1(const st2& a, const st2& b) {
return a.num < b.num;
}
int main() {
int cnt = 0;
while (scanf("%d",&n)!=EOF) {
/*cin >> n ;*/
memset(tr, 0, sizeof(tr));
memset(arr, 0, sizeof(arr));
cnt++;
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i].k);
arr[i].num = i;
}
scanf("%d", &m);
for (int i = n + 1; i <= m + n; i++) {
scanf("%d%d%d", &arr[i].l, &arr[i].r, &arr[i].k);
arr[i].num = i;
}
sort(arr + 1, arr + 1 + n + m, cmp);
/*for (int i = 1; i <= n + m; i++) {
cout << arr[i].l << " " << arr[i].r << " " << arr[i].num << " " << arr[i].k << endl;
}
cout << endl;*/
build(1, 1, n);
vector<st2>p;
for (int i = 1; i <= n + m; i++) {
if (arr[i].l==0) {
change(1, arr[i].num, arr[i].k);
}
else {
LL t = ask(1, arr[i].l, arr[i].r);
p.push_back({ t,arr[i].num });
}
}
sort(p.begin(), p.end(), cmp1);
printf("Case #%d:\n", cnt);
for (int i = 0; i < m; i++) {
printf("%lld\n", p[i].v);
}
}
return 0;
}