题目链接:https://www.luogu.org/problem/P2023
题意
对一组数列进行三种操作
- 对某一段区间的每一个数,扩大某一个倍数
- 对某一段区间的每一个数,增加某一个增量
- 询问某一段区间的和
题解:线段树
令 add 和 mul 表示区间每个数需要加上的数和乘上的数,目的是对自区间进行加法或者乘的操作,分配之后会归位为 0 和 1,表示已经分配完毕。
如果只有加法一种操作,很明显用一个lazy标记就可以得到答案,sum+=add*len,插入或查找的时候push一下,将add分配下去。
如果增加乘法操作,则:
所以将sum看成两部分,一种是加,一种是乘,而且每次只会进行其中一种操作。
- 如果是加操作,直接对结点的add值操作即可。
- 如果是乘操作,则需要如上变形。首先对 add 进行计算,更新当前 sum ,更新 mul ,需要注意 add和mul是对下一个子区间的一种操作,设子区间的当前和为sum0,则
sum0=mul*sum0+add*len
这个计算在push中体现,每一个push是对下一个子区间的操作,所以每次计算 add 的时候不能仅仅是node[num*2].add+=node[num].add ,但很明显每次乘积之后,如果子区间的add不为0,那么分配的add=add*mul,也就是多次进行加操作之后再乘,每次的加操作也就翻了一番。
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define ll long long
#define inf 1000000009000000000
#define IOS ios::sync_with_stdio(false)
ll mod;
struct e
{
ll left, right; //该节点的区间范围
ll add; //区间增量
ll mul; //区间乘量
ll sum; //区间和,包括add在内
};
e tree[4*maxn];
ll a[maxn];//记录原始值
//建树,从上往下建
ll build(ll s, ll t, ll num)
{
ll i, j;
tree[num].left = s;
tree[num].right = t;
tree[num].add = 0;
tree[num].mul=1;
if (s != t)
{
i = build(s, (s + t) / 2, num * 2);
j = build((s + t) / 2 + 1, t, num * 2 + 1);
}
else//s等于t说明是最底层
{
tree[num].sum = a[s];
return(a[s]);
}
tree[num].sum = i + j;//求和
tree[num].sum%=mod;
return(tree[num].sum);
}
void insert(ll s, ll t, ll add,ll mul,ll num)
{
//如果发现是s,t的子集,标记并修改后返回
if (s <= tree[num].left&&tree[num].right <= t)
{
tree[num].add*=mul;
tree[num].add += add;
tree[num].sum*=mul;
tree[num].sum += add * (tree[num].right - tree[num].left + 1);
tree[num].sum%=mod;
tree[num].mul*=mul;
tree[num].add%=mod;
tree[num].mul%=mod;
//cout<<"add="<<tree[num].add<<endl;
return;
}
//发现不是s,t的子集,检查是否被标记过,分散到左右子节点中,重置add
//为什么要执行这个操作,因为后面需要标记它后面的某个子集,而后面的子集原来都是没标记的,所以必须提前分配
if (tree[num].add||tree[num].mul!=1)
{
tree[num*2].sum*=tree[num].mul;
tree[num * 2].sum += tree[num].add*(tree[num * 2].right - tree[num * 2].left + 1);
tree[num*2].sum%=mod;
tree[num*2+1].sum*=tree[num].mul;
tree[num * 2 + 1].sum += tree[num].add*(tree[num * 2 + 1].right - tree[num * 2 + 1].left + 1);
tree[num*2+1].sum%=mod;
tree[num*2].mul*=tree[num].mul;
tree[num*2+1].mul*=tree[num].mul;
tree[num*2].add*=tree[num].mul;
tree[num * 2].add += tree[num].add;
tree[num*2+1].add*=tree[num].mul;
tree[num * 2 + 1].add += tree[num].add;
tree[num*2].mul%=mod;
tree[num*2+1].mul%=mod;
tree[num * 2].add %=mod;
tree[num * 2 + 1].add %=mod;
tree[num].add = 0;
tree[num].mul=1;
}
if (s <= (tree[num].left + tree[num].right) / 2)
insert(s, t, add, mul,num * 2);
if (t>(tree[num].left + tree[num].right) / 2)
insert(s, t, add, mul,num * 2 + 1);
tree[num].sum = tree[num * 2].sum + tree[num * 2 + 1].sum;
tree[num].sum%=mod;
}
ll search(ll s, ll t, ll num)
{
ll i = 0, j = 0;
//搜索到是其子集,返回sum
if (s <= tree[num].left&&tree[num].right <= t)
{
//cout<<tree[num].left<<" "<<tree[num].right<<" "<<tree[num].sum<<endl;
return(tree[num].sum);
}
//与修改一样,必须分配下去
if (tree[num].add||tree[num].mul!=1)
{
tree[num*2].sum*=tree[num].mul;
tree[num * 2].sum += tree[num].add*(tree[num * 2].right - tree[num * 2].left + 1);
tree[num*2].sum%=mod;
tree[num*2+1].sum*=tree[num].mul;
tree[num * 2 + 1].sum += tree[num].add*(tree[num * 2 + 1].right - tree[num * 2 + 1].left + 1);
tree[num*2+1].sum%=mod;
tree[num*2].mul*=tree[num].mul;
tree[num*2+1].mul*=tree[num].mul;
tree[num*2].add*=tree[num].mul;
tree[num * 2].add += tree[num].add;
tree[num*2+1].add*=tree[num].mul;
tree[num * 2 + 1].add += tree[num].add;
tree[num*2].mul%=mod;
tree[num*2+1].mul%=mod;
tree[num * 2].add %=mod;
tree[num * 2 + 1].add %=mod;
tree[num].add = 0;
tree[num].mul=1;
}
if (s <= (tree[num].left + tree[num].right) / 2)
{
i = search(s, t, num * 2);
i%=mod;
}
if (t>(tree[num].left + tree[num].right) / 2)
{
j = search(s, t, num * 2 + 1);
j%=mod;
}
return(i + j)%mod;
}
int main()
{
IOS;
ll k, n, m, p, t;
cin>>n>>mod;
for (ll i = 1; i <= n; i++)
cin>>a[i];
build(1, n, 1);
cin>>m;
for (ll i = 1; i <= m; i++)
{
ll s,a,b,c;
cin>>s;
if(s==1)
{
cin>>a>>b>>c;
c%=mod;
insert(a,b,0,c,1);
}
else if (s == 2)
{
cin>>a>>b>>c;
c%=mod;
insert(a, b, c, 1, 1);
}
else
{
cin>>a>>b;
cout << search(a, b, 1) << endl;
}
}
return(0);
}