第一道线段树题 同时维护区间乘法和区间加法

题号:luoguP3373
人生第一次写线段树QAQ 写的详细一点~
题意: 三种操作:区间乘法 区间加法 区间查询。结果取模。
主要就是要注意同时维护加法和乘法的lazytag时,加法和乘法的顺序会影响结果,如:
x*2+3 != (x+3)*2
因此 维护其中一个tag时 要同时改变另一个tag 以免去顺序的影响。
因此有两种选择 先维护乘法 和 先维护加法

假设 x节点此时乘法tag是2 加法tag是3,之后获得了乘法tag4 以及加法tag5
先维护乘法:
x=(x2)+3
获得后:乘法tag
4 加法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;
        }
    }
}

你可能感兴趣的:(第一道线段树题 同时维护区间乘法和区间加法)