题号:luoguP3373
人生第一次写线段树QAQ 写的详细一点~
题意: 三种操作:区间乘法 区间加法 区间查询。结果取模。
主要就是要注意同时维护加法和乘法的lazytag时,加法和乘法的顺序会影响结果,如:
x*2+3 != (x+3)*2
因此 维护其中一个tag时 要同时改变另一个tag 以免去顺序的影响。
因此有两种选择 先维护乘法 和 先维护加法
假设 x节点此时乘法tag是2 加法tag是3,之后获得了乘法tag4 以及加法tag5
先维护乘法:
x=(x2)+3
获得后:乘法tag4 加法tag4+5 x=(x8)+3*4+5
先维护加法:
x=(x+1.5)*2
显然涉及小数 有精度问题 不继续了
在确定先维护乘法后 就可以开始线段树了…
#include
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define endl '\n'
#define for1(I, A, B) for (int I = (A); I < (B); ++I)
#define forn(I, A, B) for (int I = (A); I <= (B); ++I)
#define pb emplace_back
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef set<int> si;
typedef double db;
const db eps=1e-8;
const db pi=acos(-1);
const ll inf=0x3f3f3f3f3f3f3f3f;
const int INF=0x3f3f3f3f;
const int MAX=1e5+10;
const ll mod2 = 1e9+7;
const ll mod=998244353;
int n,m,p;
ll a[MAX];
ll lzadd[MAX<<2],lzmul[MAX<<2],st[MAX<<2];
void build(int l,int r,int now)
{
if(l == r)
{
st[now]=a[l];
}
else
{
int mid = (l+r)>>1;
build(l,mid,now<<1);
build(mid+1,r,now<<1|1);
st[now] = (st[now<<1] + st[now<<1|1])%p;
}
}
void pushadd(int now,int L,int R)
{
int x = lzadd[now];
int mid = (L+R)>>1;
lzadd[now<<1] += x;//wa
lzadd[now<<1] %= p;
lzadd[now<<1|1] += x;
lzadd[now<<1|1] %= p;
st[now<<1] += 1ll*(mid-L+1)*x%p;//wa
st[now<<1] %= p;
st[now<<1|1] += 1ll*(R-mid)*x%p;
st[now<<1|1] %= p;
lzadd[now] = 0;
}
void pushmul(int now)
{
int x = lzmul[now];
lzmul[now<<1] *= x;
lzmul[now<<1] %= p;
lzadd[now<<1] *= x;
lzadd[now<<1] %= p;
lzmul[now<<1|1] *= x;
lzmul[now<<1|1] %= p;
lzadd[now<<1|1] *= x;
lzadd[now<<1|1] %= p;
st[now<<1] *= x;
st[now<<1] %= p;
st[now<<1|1] *= x;
st[now<<1|1] %= p;
lzmul[now] = 1;
}
void add(int l,int r,int now,ll x,int L,int R)
{
if(l<=L&&r>=R)
{
st[now] += 1ll*(R-L+1)*x%p; //wa R-L int 就是因为R L是int的 忘记*1ll了
st[now]%=p;
lzadd[now]+=x;
lzadd[now]%=p;
return ;
}
//2 3 5 6 6
if(lzmul[now]!=1)pushmul(now);
if(lzadd[now]!=0)pushadd(now,L,R);
int mid = (L+R)>>1;
if(l<=mid)
{
add(l,r,now<<1,x,L,mid);
}
if(r>mid)
{
add(l,r,now<<1|1,x,mid+1,R);
}
st[now]=st[now<<1]+st[now<<1|1];
st[now]%=p;
}
void mul(int l,int r,int now,ll x,int L,int R)
{
if(l<=L&&r>=R)
{
st[now] *= x;
st[now]%=p;
lzadd[now]*=x;
lzadd[now]%=p;
lzmul[now]*=x;
lzmul[now]%=p;
return ;
}
if(lzmul[now]!=1)pushmul(now);
if(lzadd[now]!=0)pushadd(now,L,R);
int mid = (L+R)>>1;
if(l<=mid)
{
mul(l,r,now<<1,x,L,mid);
}
if(r>mid)
{
mul(l,r,now<<1|1,x,mid+1,R);
}
st[now]=st[now<<1]+st[now<<1|1];//wa forget 忘了加这句了..
st[now]%=p;
}
ll q(int l,int r,int now,int L,int R)
{
ll ans=0;
if(l<=L&&r>=R)
{
return st[now];
}
if(lzmul[now]!=1)pushmul(now);
if(lzadd[now]!=0)pushadd(now,L,R);
int mid = (L+R)>>1;
if(l<=mid)//waµã
ans += q(l,r,now<<1,L,mid);
if(r>=mid+1)
ans += q(l,r,now<<1|1,mid+1,R);
return ans%p;
}
void show(int n) //写到一半突然跑去写了个这个 用来树状显示线段树的 日后debug用..
{
int t = 1,tt = 1;
int lg=0;
while(t<n)
{
t*=2;
lg++;
}
int now = 1;
vector<pair<int,int>> v,v2;
v.pb(make_pair(1,2*n));
forn(i,0,lg)
{
forn(j,1,t/tt/2*10-5)printf(" ");
int l=1;int r=n;
int sum = 0;
forn(j,1,tt)
{
if(j%2==1)
{
l=v[sum].first;
r=(v[sum].first+v[sum].second)/2;
l=min(l,r);
v2.pb(make_pair(l,r));
}
else
{
l=(v[sum].first+v[sum].second)/2+1;
r=v[sum].second;
l=min(l,r);
sum++;
v2.pb(make_pair(l,r));
}
printf(" %2d--%2d ",l,r);
forn(j,1,t/tt/2*10-5)printf(" ");
forn(j,1,t/tt/2*10-5)printf(" ");
}
v.clear();
v=v2;
v2.clear();
printf("\n");
forn(j,1,t/tt/2*10-5)printf(" ");
forn(j,1,tt)
{
printf(" %4d ",st[now++]);
forn(j,1,t/tt/2*10-5)printf(" ");
forn(j,1,t/tt/2*10-5)printf(" ");
}
printf("\n\n");
tt*=2;
}
}
int main()
{
//freopen("in.txt","r",stdin);
cin>>n>>m>>p;
forn(i,1,n)cin>>a[i];
build(1,n,1);
forn(i,1,n*4+5)
{
lzmul[i] = 1;
}
forn(i,1,m)
{ //show(n);
int mk;
cin>>mk;
if(mk==1)
{
int x,y,k;
cin>>x>>y>>k;
mul(min(x,y),max(x,y),1,k,1,n);
}
else if(mk == 2)
{
int x,y,k;
cin>>x>>y>>k;
add(min(x,y),max(x,y),1,k,1,n);
}
else
{
int x,y;
cin>>x>>y;
cout<<q(min(x,y),max(x,y),1,1,n)<<endl;
}
}
}