[Luogu 省选培训] 子序列

91 篇文章 1 订阅
59 篇文章 0 订阅

题目描述:

给你一串由01组成的串
操作1:将[l,r]中的数取反,即0变为1,1变为0.
操作2:查询从[l,r]这个区间中本质不同的子串个数.

题目分析:

首先通过DP得到答案
DP[i][0]表示i位置结尾为0的本质不同的子串个数
DP[i][1]表示i位置结尾为1的本质不同的子串个数
当val[i]==0时
DP[i][0]=DP[i-1][0]+DP[i-1][1]+1 DP[i][1]=DP[i-1][1]
反之
DP[i][0]=DP[i-1][0] DP[i][1]=DP[i-1][0]+DP[i-1][1]+1
用矩阵表示转移即为这里写图片描述
为什么要用矩阵表示?!
对于[f(i-1,0) f(i-1,1) 1]这个矩阵
同样可以 由[f(i-2,0) f(i-2,1) 1]这个矩阵乘一个矩阵转移而来
于是[f(i,0) f(i,1) 1]=[0 0 1] * [???] * [???] * … *[???]
我们可以把后面的矩阵结合起来(矩阵乘法符合结合律)
于是我们可以用线段树维护这些矩阵.
即0的节点为第一个矩阵,1的节点为第二个矩阵.
方法即为:线段树套矩阵!
当然还有一个问题.
那就是我们还有序列取反的问题
我们同样可以使用一个懒标记!
通过观察我们发现 第一个矩阵 经过 第一行和第二行的交换,第一列和第二列的交换即为第二个矩阵!!!
矩阵乘法满足一个规律,即两个子矩阵分别经过
第一行和第二行的交换,第一列和第二列的交换 这个过程
然后相乘得出的矩阵
即为原相乘矩阵经过相同操作后得到的矩阵,那么就可以引入懒标记了.

Ac 代码:

鄙人没有用结构体进行封装,用的函数较多 qwq
另外吐槽一句,为啥不开long long一个点都不对!

#include <cstdio>
#include <iostream>
#include <cstring>
#define il inline
#define T tree[o]
#define swap std::swap
#define ll long long
const int maxm=1e5+1;
const int mod=1e9+7;
ll ans[4][4],c[4][4];
ll x[1][3],y[1][3];
ll val[maxm];
struct Matrix{
    ll a[4][4];
    int flag;
}tree[maxm*4];
il void new_matrix(int o,int l,int r)
{
    if(val[l]==0)
     T.a[1][1]=1,T.a[2][1]=1,T.a[2][2]=1,T.a[3][1]=1,T.a[3][3]=1;
    else
     T.a[1][1]=1,T.a[1][2]=1,T.a[2][2]=1,T.a[3][2]=1,T.a[3][3]=1;
}//根据值进行矩阵初始化
il void change(int o)
{
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      ans[i][j]=T.a[i][j];
}
int flagx=1;//全局标记
il void get_matrix(int o,int l,int r)
{
    if(l>=r) return;
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      T.a[i][j]=0;//初始化矩阵
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      for(int k=1;k<=3;k++)
      {
        T.a[i][j]=(T.a[i][j]+(tree[(o<<1)].a[i][k]%mod*tree[(o<<1)|1].a[k][j]%mod)%mod)%mod;//儿子矩阵乘法
      }
}
il void cfx(int o)
{
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      c[i][j]=ans[i][j]%mod,ans[i][j]=0;
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      for(int k=1;k<=3;k++)
       ans[i][j]=(ans[i][j]+(c[i][k]%mod*tree[o].a[k][j]%mod)%mod)%mod;
}
void build(int o,int l,int r)//建线段树
{
    std::memset(T.a,0,sizeof(T.a));
    if(l>=r) new_matrix(o,l,r);
    else
    {
        int mid=(l+r)>>1;
        build((o<<1),l,mid);
        build((o<<1)|1,mid+1,r);
    }
    get_matrix(o,l,r);
}
il void update(int o)//进行交换操作
{
    if(tree[o].flag==1) tree[o].flag=0;//两次取反为不做
    else tree[o].flag=1;
    swap(T.a[1][1],T.a[2][1]),swap(T.a[1][2],T.a[2][2]),swap(T.a[1][3],T.a[2][3]);
    swap(T.a[1][1],T.a[1][2]),swap(T.a[2][1],T.a[2][2]),swap(T.a[3][1],T.a[3][2]);
}
il void pushdown(int o)//标记下放
{
    if(!tree[o].flag) return;
    tree[o].flag=0;
    update((o<<1)),update((o<<1)|1);
}
void modify(int o,int l,int r,int ql,int qr)//取反操作
{
    if(r<ql||l>qr) return;
    if(l>=ql&&r<=qr) 
    {
        update(o);
        return;
    }
    pushdown(o);
    int mid=(l+r)>>1;
    modify((o<<1),l,mid,ql,qr);
    modify((o<<1)+1,mid+1,r,ql,qr);
    get_matrix(o,l,r);
}
void query(int o,int l,int r,int ql,int qr)
{
    if(r<ql||l>qr) return;
    if(l>=ql&&r<=qr)
    {
        if(flagx) change(o),flagx=0;
        else cfx(o);
        return;
    }
    pushdown(o);
    int mid=(l+r)>>1;
    query((o<<1),l,mid,ql,qr);
    query((o<<1)|1,mid+1,r,ql,qr);
}
il ll Ans(int l,int r,int n)
{
    flagx=1; 
    x[1][1]=y[1][1]=0,x[1][2]=y[1][2]=0,x[1][3]=1,y[1][3]=0;
    query(1,1,n,l,r);
    for(int i=1;i<=1;i++)
     for(int j=1;j<=3;j++)
      for(int k=1;k<=3;k++)
       y[i][j]=(y[i][j]+(x[i][k]%mod*ans[k][j]%mod)%mod)%mod;
    return (y[1][1]+y[1][2])%mod;//最后 f(i,0)+f(i,1)即为答案
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     scanf("%d",&val[i]);
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        int opt,l,r;
        scanf("%d%d%d",&opt,&l,&r);
        //printf("%d %d %d %d %d\n",opt,l,r,i,m);
        if(opt==1) modify(1,1,n,l,r);
        else printf("%lld\n",Ans(l,r,n));
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值