线段树、树状数组例题
1.17刚练的线段树和树状数组,热乎着~
洛谷3373 线段树2
题目描述
如题,已知一个数列,你需要进行下面三种操作:
将某区间每一个数乘上 x
将某区间每一个数加上 x
求出某区间每一个数的和
输入格式
第一行包含三个整数 n,m,p分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 n个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含若干个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y]内每个数乘上 k
操作 2: 格式:2 x y k 含义:将区间 [x,y]内每个数加上 k
操作 3: 格式:3 x y 含义:输出区间 [x,y] 内每个数的和对 p 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 3的结果。
输入输出样例
输入
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
输出
17
2
这里 注意
1.laz[ ].add的标记要 * 区间范围;
2.对于所有运算的地方取模,防止爆long long;
3.这种纯考数据结构的题目,调起来一定要细心,自闭可以,但不能影响自己调试。
谢谢 lwh哥哥 安慰+帮忙调试。
“我已经是一个成熟的ftx了要自己调代码”;
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll tree[400050],d[100050];
ll mod;
struct node{
ll add=0,mul=1;
}laz[400050];
void build(ll p,ll l,ll r)
{
// printf("%lld %lld %lld\n",p,l,r);
if(l==r)
{
tree[p]=d[l];
return;
}
ll mid=(l+r)>>1;
build(2*p,l,mid);
build(2*p+1,mid+1,r);
tree[p]=(tree[2*p]+tree[2*p+1])%mod;
}
void pushdown(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
laz[2*p].add=(laz[2*p].add*laz[p].mul%mod +laz[p].add)%mod;
laz[2*p+1].add=(laz[2*p+1].add*laz[p].mul%mod +laz[p].add)%mod;
laz[2*p].mul=laz[2*p].mul*laz[p].mul%mod;
laz[2*p+1].mul=laz[2*p+1].mul*laz[p].mul%mod;
tree[2*p]=(tree[2*p]*laz[p].mul%mod+laz[p].add*(mid-l+1))%mod;
tree[2*p+1]=(tree[1+2*p]*laz[p].mul%mod+laz[p].add*(r-mid))%mod;
laz[p].add=0;laz[p].mul=1;
}
void update(ll p,ll l,ll r,ll x,ll y,ll w,ll o){
//printf("%lld %lld %lld %lld %lld %lld %lld\n",p,l,r,x,y,w,o);
if(x<=l&&r<=y){
if(o==2)
{
laz[p].add+=w;
tree[p]=(tree[p]+w*(r-l+1))%mod;
}
else if(o==1)
{
laz[p].mul*=w%=mod;
laz[p].add=(laz[p].add)*w%mod;
tree[p]=tree[p]*w%mod;
}
return;
}
pushdown(p,l,r);
ll mid=(l+r)>>1;
if(x<=mid)update(2*p,l,mid,x,y,w,o);
if(mid<y)update(2*p+1,mid+1,r,x,y,w,o);
tree[p]=(tree[2*p]+tree[2*p+1])%mod;
}
ll query(ll p,ll l,ll r,ll x,ll y){
if(x<=l&&r<=y)
{
return tree[p]%mod;
}
pushdown(p,l,r);
ll ans=0,mid=(l+r)>>1;
if(x<=mid)ans+=query(2*p,l,mid,x,y);
if(mid<y)ans+=query(2*p+1,mid+1,r,x,y);
return ans%mod;
}
int main()
{
ll n,m,i,j,o,a,b,c;
scanf("%lld%lld%lld",&n,&m,&mod);
for(i=1;i<=n;i++)
{
scanf("%lld",&d[i]);
// printf("%lld ",d[i]);
// cout<<endl;
}
build(1,1,n);
for(i=1;i<=m;i++)
{
scanf("%lld",&o);
if(o==1)
{
scanf("%lld%lld%lld",&a,&b,&c);
update(1,1,n,a,b,c,1);
}
else if(o==2)
{
scanf("%lld%lld%lld",&a,&b,&c);
update(1,1,n,a,b,c,2);
}
else
{
scanf("%lld%lld",&a,&b);
a=query(1,1,n,a,b);
printf("%lld\n",a%mod);
}
}
return 0;
}
Matrix
Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row and j-th column. Initially we have A[i, j] = 0 (1 <= i, j <= N).
We can change the matrix in the following way. Given a rectangle whose upper-left corner is (x1, y1) and lower-right corner is (x2, y2), we change all the elements in the rectangle by using “not” operation (if it is a ‘0’ then change it into ‘1’ otherwise change it into ‘0’). To maintain the information of the matrix, you are asked to write a program to receive and execute two kinds of instructions.
- C x1 y1 x2 y2 (1 <= x1 <= x2 <= n, 1 <= y1 <= y2 <= n) changes the matrix by using the rectangle whose upper-left corner is (x1, y1) and lower-right corner is (x2, y2).
- Q x y (1 <= x, y <= n) querys A[x, y].
Input
The first line of the input is an integer X (X <= 10) representing the number of test cases. The following X blocks each represents a test case.
The first line of each block contains two numbers N and T (2 <= N <= 1000, 1 <= T <= 50000) representing the size of the matrix and the number of the instructions. The following T lines each represents an instruction having the format “Q x y” or “C x1 y1 x2 y2”, which has been described above.
Output
For each querying output one line, which has an integer representing A[x, y].
There is a blank line between every two continuous test cases.
Sample Input
1
2 10
C 2 1 2 2
Q 2 2
C 2 1 2 1
Q 1 1
C 1 1 2 1
C 1 2 1 2
C 1 1 2 2
Q 1 1
C 1 1 2 1
Q 2 1
Sample Output
1
0
0
1
这是一道二维树状数组,其实维护的是一个二维差分数组,实现区间修改和单点查询的操作。
上代码~
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
ll tree[1005][1005];
ll n,m;
void add(ll x,ll y,ll z){
ll yy=y;
while(x<=n){
y=yy;
while(y<=n){
tree[x][y]+=z;
y+=y&-y;
}
x+=x&-x;
}
}
void aadd(ll x1,ll y1,ll x2,ll y2){
add(x1,y1,1);
add(x1,y2+1,1);
add(x2+1,y1,1);
add(x2+1,y2+1,1);
}
ll ask(ll x,ll y){
ll ans=0,yy=y;
while(x){
y=yy;
while(y){
ans+=tree[x][y];
y-=y&-y;
}
x-=x&-x;
}
return ans;
}
int main()
{
ll i,j,k,t,a,x1,x2,y1,y2;
char s[10];
scanf("%lld",&t);
while(t--){
scanf("%lld %lld",&n,&m);
memset(tree,0,sizeof(tree));
for(i=1;i<=m;i++){
scanf("%s",s);
if(s[0]=='C'){
scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2);
aadd(x1,y1,x2,y2);
}else {
scanf("%lld %lld",&x1,&y1);
a=ask(x1,y1);
printf("%lld\n",a%2);
}
}
printf("\n");
}
return 0;
}
Transformation
Yuanfang is puzzled with the question below:
There are n integers, a1, a2, …, an. The initial values of them are 0. There are four kinds of operations.
Operation 1: Add c to each number between ax and ay inclusive. In other words, do transformation ak<—ak+c, k = x,x+1,…,y.
Operation 2: Multiply c to each number between ax and ay inclusive. In other words, do transformation ak<—ak×c, k = x,x+1,…,y.
Operation 3: Change the numbers between ax and ay to c, inclusive. In other words, do transformation ak<—c, k = x,x+1,…,y.
Operation 4: Get the sum of p power among the numbers between ax and ay inclusive. In other words, get the result of axp+ax+1p+…+ay p.
Yuanfang has no idea of how to do it. So he wants to ask you to help him.
Input
There are no more than 10 test cases.
For each case, the first line contains two numbers n and m, meaning that there are n integers and m operations. 1 <= n, m <= 100,000.
Each the following m lines contains an operation. Operation 1 to 3 is in this format: “1 x y c” or “2 x y c” or “3 x y c”. Operation 4 is in this format: “4 x y p”. (1 <= x <= y <= n, 1 <= c <= 10,000, 1 <= p <= 3)
The input ends with 0 0.
Output
For each operation 4, output a single integer in one line representing the result. The answer may be quite large. You just need to calculate the remainder of the answer when divided by 10007.
Sample Input
5 5
3 3 5 7
1 2 4 4
4 1 5 2
2 2 5 8
4 3 5 3
0 0
Sample Output
307
7489
因为题目中有多组样例,记得在build函数中,初始化laz[ ]数组的值(在这里犯傻了,调了很久)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 10007
ll tree[400050],tree2[400050],tree3[400050],d[100050];
struct node{
ll add,mul;
}laz[400050];
void build(ll p,ll l,ll r)
{
laz[p].add=0;
laz[p].mul=1;
if(l==r)
{
laz[p].add=0;
laz[p].mul=1;
tree[p]=0;
tree2[p]=0;
tree3[p]=0;
return;
}
ll mid=(l+r)>>1;
build(2*p,l,mid);
build(2*p+1,mid+1,r);
tree[p]=(tree[2*p]+tree[2*p+1])%mod;
tree2[p]=(tree2[2*p]+tree2[2*p+1])%mod;
tree3[p]=(tree3[2*p]+tree3[2*p+1])%mod;
}
void pushdown(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
tree[2*p]=(tree[2*p]*laz[p].mul)%mod;
tree[2*p+1]=(tree[2*p+1]*laz[p].mul)%mod;
tree2[2*p]=(tree2[2*p]*laz[p].mul%mod*laz[p].mul%mod);
tree2[2*p+1]=(tree2[2*p+1]*laz[p].mul%mod*laz[p].mul%mod);
tree3[2*p]=(tree3[2*p]*laz[p].mul%mod*laz[p].mul%mod*laz[p].mul%mod);
tree3[2*p+1]=(tree3[2*p+1]*laz[p].mul%mod*laz[p].mul%mod*laz[p].mul%mod);
laz[2*p].mul=laz[2*p].mul*laz[p].mul%mod;
laz[2*p+1].mul=laz[2*p+1].mul*laz[p].mul%mod;
//printf("1 %lld %lld\n",tree3[6],tree2[6]);
//printf("1 %lld %lld\n",laz[p].add,tree3[6]);
tree3[2*p]=(tree3[2*p]+3*laz[p].add%mod*tree2[2*p]%mod+3*laz[p].add*laz[p].add%mod*tree[2*p]%mod+(mid-l+1)*laz[p].add*laz[p].add%mod*laz[p].add%mod)%mod;
//printf("2 %lld %lld\n",tree2[6],tree3[6]);
tree3[2*p+1]=(tree3[2*p+1]+3*laz[p].add%mod*tree2[2*p+1]%mod+3*laz[p].add*laz[p].add%mod*tree[2*p+1]%mod+(r-mid)*laz[p].add*laz[p].add%mod*laz[p].add%mod)%mod;
tree2[2*p]=(tree2[2*p]+2*laz[p].add%mod*tree[2*p]%mod+(mid-l+1)*laz[p].add*laz[p].add%mod)%mod;
tree2[2*p+1]=(tree2[2*p+1]+2*laz[p].add%mod*tree[2*p+1]%mod+(r-mid)*laz[p].add*laz[p].add%mod)%mod;
tree[2*p]=(tree[2*p]+(mid-l+1)*laz[p].add%mod)%mod;
tree[2*p+1]=(tree[2*p+1]+(r-mid)*laz[p].add%mod)%mod;
laz[2*p].add=(laz[2*p].add*laz[p].mul%mod+laz[p].add)%mod;
laz[2*p+1].add=(laz[2*p+1].add*laz[p].mul%mod+laz[p].add)%mod;
//printf("pb:%lld %lld %lld\n",p,tree2[2*p],tree3[2*p]);
laz[p].add=0;laz[p].mul=1;
}
void update(ll p,ll l,ll r,ll x,ll y,ll w,ll o){
if(x<=l&&r<=y){
if(o==1)
{
laz[p].add+=w;
tree3[p]=(tree3[p]+3*w%mod*tree2[p]%mod+3*w%mod*w%mod*tree[p]%mod+(r-l+1)*w%mod*w%mod*w%mod)%mod;
tree2[p]=(tree2[p]+2*w%mod*tree[p]%mod+(r-l+1)*w*w%mod)%mod;
tree[p]=(tree[p]+(r-l+1)*w%mod)%mod;
}
else if(o==2)
{
laz[p].mul=laz[p].mul*w%mod;
laz[p].add=(laz[p].add)*w%mod;
tree[p]=(tree[p]*w)%mod;
tree2[p]=(tree2[p]*w%mod*w%mod);
tree3[p]=(tree3[p]*w%mod*w%mod*w%mod);
}else{
laz[p].mul=0;
laz[p].add=w;
tree[p]=(w*(r-l+1))%mod;
tree2[p]=(w*w%mod*(r-l+1))%mod;
tree3[p]=(w*w%mod*w%mod*(r-l+1))%mod;
}
return;
}
pushdown(p,l,r);
ll mid=(l+r)>>1;
if(x<=mid)update(2*p,l,mid,x,y,w,o);
if(mid<y)update(2*p+1,mid+1,r,x,y,w,o);
tree[p]=(tree[2*p]+tree[2*p+1])%mod;
tree2[p]=(tree2[2*p]+tree2[2*p+1])%mod;
tree3[p]=(tree3[2*p]+tree3[2*p+1])%mod;
}
ll query(ll p,ll l,ll r,ll x,ll y,ll o){
if(x<=l&&r<=y)
{
if(o==1)return tree[p]%mod;
if(o==2)return tree2[p]%mod;
return tree3[p]%mod;
}
pushdown(p,l,r);
ll ans=0,mid=(l+r)>>1;
if(x<=mid)ans+=query(2*p,l,mid,x,y,o);
if(mid<y)ans+=query(2*p+1,mid+1,r,x,y,o);
return ans%mod;
}
int main()
{
ll n,m,i,j,o,a,b,c;
while(~scanf("%lld%lld",&n,&m))
{
if(n==0&&m==0)break;
build(1,1,n);
for(i=1;i<=m;i++)
{
scanf("%lld%lld%lld%lld",&o,&a,&b,&c);
if(o==1)
{
update(1,1,n,a,b,c,1);
}
else if(o==2)
{
update(1,1,n,a,b,c,2);
}
else if(o==3)
{
update(1,1,n,a,b,c,3);
}
else
{
a=query(1,1,n,a,b,c);
printf("%lld\n",a);
}
}
}
return 0;
}
这道题是上面那道洛谷题目的hard version,对于加减和乘,还是按照 ax+b 的法则,修改laz[ ]标记;对于赋值操作,将laz[ ]标记的add 赋值为需要改变的量 k,将mul赋值为0,代表清空原值的操作(这个简直是神来之笔)。
最难的是,p次方操作,这边的想法就是维护三棵树,分别是一次方,二次方和三次方,对于乘和赋值,三棵树差不多相同,麻烦的是相加操作。已知
(
x
+
c
)
3
(x+c)^3
(x+c)3=
x
3
x^3
x3+3
c
2
c^2
c2 x+3c
x
2
x^2
x2+
c
3
c^3
c3,所以sum3变化后的值为sum3+3*
c
2
c^2
c2sum2+3c*sum1+
c
3
c^3
c3(r-l+1);sum2和sum1 同理维护。
所以这里还有个小细节,在每次修改的时候,先修改三次方再修改二次方,最后是一次方。
**