线段树常用技巧模版(刷题篇)
Posted KaaaterinaX
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树常用技巧模版(刷题篇)相关的知识,希望对你有一定的参考价值。
这篇博客相当于线段树学习博客的补充。
原文链接:2021-07-27 重见线段树
一、双tag线段树
这种时候,如果不做特殊处理,tag添加的顺序将影响最终结果,十分麻烦。
由于操作含有乘法和加法,并且先乘后加与先加后乘结果不同。所以标记下传的时候要这样处理:
void pushdown(int u){
//先乘后加,并下传懒标记
tr[u<<1].sum=(tr[u<<1].sum*tr[u].mu)%p;
tr[u<<1].sum=(tr[u<<1].sum+(tr[u<<1].r-tr[u<<1].l+1)*tr[u].ad)%p;
tr[u<<1].ad=(tr[u].ad+tr[u<<1].ad*tr[u].mu)%p;//子节点需要ad标记的更新与父节点的mu有关
tr[u<<1].mu=(tr[u<<1].mu*tr[u].mu)%p;
tr[u<<1|1].sum=(tr[u<<1|1].sum*tr[u].mu)%p;
tr[u<<1|1].sum=(tr[u<<1|1].sum+(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].ad)%p;
tr[u<<1|1].ad=(tr[u].ad+tr[u<<1|1].ad*tr[u].mu)%p;
tr[u<<1|1].mu=(tr[u<<1|1].mu*tr[u].mu)%p;
//父节点懒标记回归默认
tr[u].ad=0;
tr[u].mu=1;
}
void modify1(int u,ll l,ll r,ll k){
//区间*k
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].sum=(tr[u].sum*k)%p;
tr[u].ad=(tr[u].ad*k)%p;
tr[u].mu=(tr[u].mu*k)%p;
return;
}
if(tr[u].r<l||tr[u].l>r){
return;
}
pushdown(u);
modify1(u<<1,l,r,k);
modify1(u<<1|1,l,r,k);
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
啊记得开ll。。
这种大数据都必须开ll,不然wa到怀疑人生。
完整代码如下:
#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
//#include <unordered_set>
#include <string.h>
#include <map>
//#include <unordered_map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#include <deque>
#define YES "YES"
#define NO "NO"
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define FAST ios::sync_with_stdio(false)
#define ll long long
#define pb push_back
#define endl '\\n'
#define eps const double eps=1e-6
using namespace std;
//---------------------------------------------------//
template<typename T>inline void Re(T &x){
x=0;
char c=getchar();
ll f=1;
if(c>'9'||c<'0'){
if(c=='-'){
f=-1;
}
c=getchar();
}
while(c<='9'&&c>='0'){
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
x*=f;
}
template<typename T> inline void write(T x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9){
write(x/10);
}
putchar(x%10+'0');
}
//_____________________________________________________//
const int maxn=1e5+7;
int n,m;
ll p;
ll a[maxn];
struct tr{
ll l,r;
ll sum;
ll ad,mu;//懒标记
}tr[maxn<<2];
void build(int u,ll l,ll r){
tr[u].l=l;
tr[u].r=r;
tr[u].mu=1;
if(l==r){
tr[u].sum=a[l];
return;
}
ll mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void pushdown(int u){
//先乘后加,并下传懒标记
tr[u<<1].sum=(tr[u<<1].sum*tr[u].mu)%p;
tr[u<<1].sum=(tr[u<<1].sum+(tr[u<<1].r-tr[u<<1].l+1)*tr[u].ad)%p;
tr[u<<1].ad=(tr[u].ad+tr[u<<1].ad*tr[u].mu)%p;
tr[u<<1].mu=(tr[u<<1].mu*tr[u].mu)%p;
tr[u<<1|1].sum=(tr[u<<1|1].sum*tr[u].mu)%p;
tr[u<<1|1].sum=(tr[u<<1|1].sum+(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].ad)%p;
tr[u<<1|1].ad=(tr[u].ad+tr[u<<1|1].ad*tr[u].mu)%p;
tr[u<<1|1].mu=(tr[u<<1|1].mu*tr[u].mu)%p;
//父节点懒标记回归默认
tr[u].ad=0;
tr[u].mu=1;
}
void modify1(int u,ll l,ll r,ll k){
//区间*k
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].sum=(tr[u].sum*k)%p;
tr[u].ad=(tr[u].ad*k)%p;
tr[u].mu=(tr[u].mu*k)%p;
return;
}
if(tr[u].r<l||tr[u].l>r){
return;
}
pushdown(u);
modify1(u<<1,l,r,k);
modify1(u<<1|1,l,r,k);
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void modify2(int u,ll l,ll r,ll k){
//区间+k
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].sum=(tr[u].sum+(tr[u].r-tr[u].l+1)*k)%p;
tr[u].ad=(tr[u].ad+k)%p;
return;
}
if(tr[u].r<l||tr[u].l>r){
return;
}
pushdown(u);
modify2(u<<1,l,r,k);
modify2(u<<1|1,l,r,k);
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
ll query(int u,ll l,ll r){
ll sum=0;
if(tr[u].l>=l&&tr[u].r<=r){
sum+=tr[u].sum;
return sum;
}
if(tr[u].r<l||tr[u].l>r){
return 0;
}
pushdown(u);
sum+=query(u<<1,l,r);
sum+=query(u<<1|1,l,r);
return sum;
}
int main(){
cin>>n>>m>>p;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
while(m--){
int x;
cin>>x;
if(x==1){
//区间内乘k
ll l,r,k;
cin>>l>>r>>k;
modify1(1,l,r,k);
}
if(x==2){
ll l,r,k;
cin>>l>>r>>k;
modify2(1,l,r,k);
}
if(x==3){
ll l,r;
cin>>l>>r;
cout<<query(1,l,r)%p<<endl;
}
}
}
以上是关于线段树常用技巧模版(刷题篇)的主要内容,如果未能解决你的问题,请参考以下文章