题解 P3373 【【模板】线段树 2】

· · 题解

(这是一道超级水的模板题)

看到好多用运算优先级强制转化的题解,感觉挺巧妙的,但是我看到这个题的时候,完全没往这上面想

(就是完全没想到)

然后我想起了一条以前做的精(sang)妙(xin)绝(bing)伦(kuang)的题目,那条题目要维护三个区间和,好多修改操作,天真的我线段树WA成了**......

言归正传来讲讲这道题

实际上,本题的难点在于加法操作与乘法操作交替进行时的运算顺序会乱,所以就有了运算优先级规定的想法

但是——

假如再来几个操作不就很难处理了吗

所以,我要做的是——将操作统一!

怎么统一?

矩阵乘法!

-----------------------------------------------分割线----------------------------------------------

前置知识时间:线段树维护矩阵

基本概念请右转P1939 矩阵加速(又是模板题.jpg)

好了,学完矩阵乘法的你应该明白了它的基本性质,那么,线段树如何维护一个矩阵呢?

由于矩阵乘法满足结合律,因此,我们可以将线段树中的信息,懒标记的信息,以及每次修改操作都改为以矩阵实现

举个例子

线段树维护这样一个表格

s1 s2
s3 s4

表(1)

而每次的修改操作为区间乘上

a1 a2
a3 a4

表(2)

那么我们的懒标记也可以记录这样为的一个表格

laz1 laz2
laz3 laz4

表(3)

当整体修改区间时用(1)与(2)分别乘(3)

而下传标记时,即同样类似于修改操作进行乘法

上传子节点信息直接把子节点信息加给父亲节点即可

(其实说白了就是把原来的加法等操作改成矩阵乘法即可)

至于正确性,由于矩阵乘法满足结合律,那么在矩阵乘法操作的时候,将多个矩乘的操作先乘在标记上,然后下传的时候再乘在线段树的子节点上,这样子与暴力处理时,一个一个乘比较,显然就是运用了一个结合律

-----------------------------------------------分割线----------------------------------------------

滚回来讲这道题

上文说到我们要统一操作,讲了这么多想法已经很明显了

构造矩阵!

观察到本题由乘法与加法构成,因此我们计算时用到的值为原来的区间和以及区间长度 (废话)

那么我们构造矩阵

s l

s代表维护的区间和,l代表该区间的长度

而每次的修改操作也是乘上一个矩阵

例如加法

1 0
x 1

而乘法的矩阵长这样

x 0
0 1

每个点的懒标记也是一个矩阵,初始如下

1 0
0 1

(为了保证该矩阵为一个“1”值,即乘上以后不变)

(上面几个矩阵乘下来的结果请读者自行验证)

那么按照上文所说把常规的线段树加法改成矩阵乘法即可

这种做法维护之后就可以将操作完全统一啦,这样子就再也不用烦先加还是先乘了,大家都是矩乘,平等对待 (还可以少写一个修改操作)

(注意:在子节点信息合并时当然是直接相加上传了,别想太多......)

PS:1.矩乘常数巨大,用循环过不了,所以得把循环拆开,详见代码

2.码风略丑将就着看看

完结撒花!

#include<bits/stdc++.h>
#define maxn 100010 
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
struct node{
    int l,r;
    ll s[2],p[2][2];
};
node tr[maxn<<2];
ll a[maxn];
int n,m,p;
void build(int x,int l,int r)
{
    tr[x].l=l;
    tr[x].r=r;
    tr[x].p[0][0]=1,tr[x].p[0][1]=0,tr[x].p[1][0]=0,tr[x].p[1][1]=1;
    if(l==r)
    {
        tr[x].s[0]=a[l];
        tr[x].s[1]=1;
        return;
    }
    int mid=l+r>>1,lc=x<<1,rc=x<<1|1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    tr[x].s[0]=(tr[lc].s[0]+tr[rc].s[0])%p;
    tr[x].s[1]=r-l+1;
}
void calc1(ll a[2],ll b[2][2])
{
    ll tmp[2]={0,0};
    tmp[0]=(a[0]*b[0][0]+a[1]*b[1][0])%p;
    tmp[1]=(a[0]*b[0][1]+a[1]*b[1][1])%p;
    a[0]=tmp[0];
    a[1]=tmp[1];
}
void calc2(ll a[2][2],ll b[2][2])
{
    ll tmp[2][2];
    tmp[0][0]=(a[0][0]*b[0][0]+a[0][1]*b[1][0])%p;
    tmp[0][1]=(a[0][0]*b[0][1]+a[0][1]*b[1][1])%p;
    tmp[1][0]=(a[1][0]*b[0][0]+a[1][1]*b[1][0])%p;
    tmp[1][1]=(a[1][0]*b[0][1]+a[1][1]*b[1][1])%p;
    a[0][0]=tmp[0][0];
    a[0][1]=tmp[0][1];
    a[1][0]=tmp[1][0];
    a[1][1]=tmp[1][1];
}
void down(int x)
{
    if(tr[x].p[0][0]==1&&tr[x].p[0][1]==0&&tr[x].p[1][0]==0&&tr[x].p[1][1]==1) return;
    int mid=tr[x].l+tr[x].r>>1,lc=x<<1,rc=x<<1|1;
    calc1(tr[lc].s,tr[x].p);
    calc1(tr[rc].s,tr[x].p);
    calc2(tr[lc].p,tr[x].p);
    calc2(tr[rc].p,tr[x].p);
    tr[x].p[0][0]=1,tr[x].p[0][1]=0,tr[x].p[1][0]=0,tr[x].p[1][1]=1;
}
void add(int x,int l,int r,ll v[2][2])
{
    if(l<=tr[x].l&&tr[x].r<=r)
    {
        calc1(tr[x].s,v);
        calc2(tr[x].p,v);
        return;
    }
    down(x);
    int mid=tr[x].l+tr[x].r>>1,lc=x<<1,rc=x<<1|1;
    if(l<=mid) add(lc,l,r,v);
    if(mid<r) add(rc,l,r,v);
    tr[x].s[0]=(tr[lc].s[0]+tr[rc].s[0])%p;
}
ll query(int x,int l,int r)
{
    if(l<=tr[x].l&&tr[x].r<=r) return tr[x].s[0];
    down(x);
    int mid=tr[x].l+tr[x].r>>1,lc=x<<1,rc=x<<1|1;
    ll tmp=0;
    if(l<=mid) tmp=query(lc,l,r);
    if(mid<r) tmp+=query(rc,l,r);
    return tmp%p;
}
int main()
{
    scanf("%d%d%lld",&n,&m,&p);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        int op,l,r;
        ll x;
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%lld",&l,&r,&x);
            ll v[2][2]={x,0,0,1};
            add(1,l,r,v);
        }
        if(op==2)
        {
            scanf("%d%d%lld",&l,&r,&x);
            ll v[2][2]={1,0,x,1};
            add(1,l,r,v);
        }
        if(op==3)
        {
            scanf("%d%d",&l,&r);
            printf("%lld\n",query(1,l,r));
        }
    }
}