【XSY2538】/【HDU6155】Subsequence Count(矩阵乘法+线段树)

题目翻译

Description

给定一个\(01\)串 \(S_{1...n}\) 和 \(Q\) 个操作。

操作有两种类型:

1、将 \([l,r]\) 区间的数取反(将其中的\(0\)变成\(1\),\(1\)变成\(0\))。

2、询问字符串 \(S\) 的子串 \(S_{l...r}\) 有多少个不同的子序列。由于答案可能很大,请将答案对 \(10^9+7\) 取模。

在数学中,某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。

Input

第一行包含两个整数 \(N\) 和 \(Q\) ,分别表示字符串长度和操作次数。

第二行包含一个字符串 \(S\) 。

接下来 \(Q\) 行,每行3个整数 \(type\),\(l\),\(r\) ,其中 \(type\) 表示操作类型, \(l\),\(r\) 表示操作区间为 \([l,r]\) 。

Output

对于每一个 \(type=2\) 的询问,输出一个整数表示答案。由于答案可能很大,请将答案对 \(10^9+7\) 取模。

Sample Input

4 4
1010
2 1 4
2 2 4
1 2 3
2 1 4

Sample Output

11
6
8

HINT

数据范围与约定

对于\(5\%\)的数据, \(N≤20\),\(Q=1\)

对于\(10\%\)的数据, \(N≤1000\),\(Q=1\)

对于\(20\%\)的数据, \(N≤10^5,Q≤10\)

对于另外\(30\%\)的数据, \(1≤N≤10^5\),\(1≤Q≤10^5\),\(type=2\)

对于\(100\%\)的数据, \(1≤N≤10^5\),\(1≤Q≤10^5\)

题解

看到求子序列,我们先想到怎么求静态的\(S\)的每个前缀子序列个数。

设\(dp[i][j]\)为\(S\)的前\(i\)位以\(j\)结尾的子序列个数是多少(\(j=0\)或\(1\))。

则当第\(i\)位为\(0\)时,\(dp\)的状态转移方程为:

\[\begin{aligned} & dp[i][0]=dp[i-1][0]+dp[i-1][1]+1\\ & dp[i][1]=dp[i-1][1]\end{aligned}\]

当第\(i\)位为\(1\)时同理,把方程的\(0\)和\(1\)反过来就好了。

我们又看到题目问的是区间修改和区间查询,就想到线段树。

关键是如何求出区间的子序列个数。

再看回\(dp\)式,自然而然地想到用矩阵维护\(dp[i-1]\)和\(dp[i]\)间的关系。

不妨把第\(i\)位的转移矩阵\(A_i\)推一下:

  1. 当第\(i\)位为0时,矩阵为:

    \[
    \begin{pmatrix}
    0 & 0 & 0 \ 0 & 0 & 0\ dp[i-1][0] & dp[i-1][1] & 1
    \end{pmatrix}
    \times
    \begin{pmatrix}
    1 & 0 & 0 \ 1 & 1 & 0\ 1 & 0 & 1
    \end{pmatrix}=
    \begin{pmatrix}
    0 & 0 & 0 \ 0 & 0 & 0\ dp[i][0] & dp[i][1] & 1
    \end{pmatrix}
    \]

  2. 当第\(i\)位为\(1\)时,矩阵为:

    \[
    \begin{pmatrix}
    0 & 0 & 0 \ 0 & 0 & 0\ dp[i-1][0] & dp[i-1][1] & 1
    \end{pmatrix}
    \times
    \begin{pmatrix}
    1 & 1 & 0 \ 0 & 1 & 0\ 0 & 1 & 1
    \end{pmatrix}=
    \begin{pmatrix}
    0 & 0 & 0 \ 0 & 0 & 0\ dp[i][0] & dp[i][1] & 1
    \end{pmatrix}
    \]

又由于初始时\(dp[0][0]=dp[0][1]=0\),所以我们就不用设起始矩阵。

那么对于\(dp[i]\)所对应的矩阵,即为:

\[A_1\times A_2\times ...\times A_i\]

然后,我们再进一步地往题目的方向推,如果要求\(S_{l...r}\)的子序列个数呢?

那我们不妨把这\(S_{l...r}\)单独看成一个串,根据上面推得的结论,\(S_{l...r}\)的子序列个数所对应矩阵即为:

\[A_l\times A_{l+1}\times ...\times A_r\]

那么我们就可以用矩阵乘法维护询问区间的子序列个数了。

至于修改,我们可以用区间懒标记\(rev\),来记录该区间的\(0\)、\(1\)是否翻转。

翻转过后,对应的矩阵也要变化,这里我们只要将矩阵的第一列与第二列交换后再将第一行与第二行交换就好了。

证明:

来自这篇博客,网址:https://www.cnblogs.com/iRedBean/p/7398272.html


最后的代码:

#include<bits/stdc++.h>

#define N 100010
#define mod 1000000007

using namespace std;

struct Matrix
{
    int a[4][4];
}zero,one,p,t[N<<2],none;

inline Matrix operator * (Matrix a,Matrix b)
{
    Matrix c=none;
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            for(int k=1;k<=3;k++)
                c.a[i][j]=(c.a[i][j]+1ll*a.a[i][k]*b.a[k][j])%mod;
    return c;
}

int n,q,rev[N<<2];
char s[N];

inline int read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')
        ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+(ch^'0');
        ch=getchar();
    }
    return x;
}

inline void init()
{
    zero.a[1][1]=zero.a[2][1]=zero.a[3][1]=zero.a[2][2]=zero.a[3][3]=1;//第i位为0的时候所对应矩阵
    one.a[1][1]=one.a[1][2]=one.a[2][2]=one.a[3][2]=one.a[3][3]=1;//第i位为1的时候所对应矩阵
    p.a[1][1]=p.a[2][2]=p.a[3][3]=1;//单位矩阵
}

inline void up(int k)
{
    t[k]=t[k<<1]*t[k<<1|1];
}

inline void build(int k,int l,int r)
{
    if(l==r)
    {
        if(s[l]=='0')
            t[k]=zero;
        else
            t[k]=one;
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    up(k);
}

inline void reverse(int k)
{
    rev[k]^=1;//打标记
    for(register int i=1;i<=3;i++)//换列
        swap(t[k].a[i][1],t[k].a[i][2]);
    for(register int i=1;i<=3;i++)//换行
        swap(t[k].a[1][i],t[k].a[2][i]);
}

inline void down(int k)//下传懒标记
{
    if(rev[k])
    {
        reverse(k<<1);
        reverse(k<<1|1);
        rev[k]=0;
    }
}

inline void change(int k,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
    {
        reverse(k);
        return;
    }
    down(k);
    int mid=(l+r)>>1;
    if(L<=mid)change(k<<1,l,mid,L,R);
    if(R>mid)change(k<<1|1,mid+1,r,L,R);
    up(k);
}

inline Matrix query(int k,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
        return t[k];
    down(k);
    int mid=(l+r)>>1;
    if(R<=mid)return query(k<<1,l,mid,L,R);
    if(L>mid)return query(k<<1|1,mid+1,r,L,R);
    return query(k<<1,l,mid,L,R)*query(k<<1|1,mid+1,r,L,R);
}

int main()
{
    init();
    n=read(),q=read();
    scanf("%s",s+1);
    build(1,1,n);
    while(q--)
    {
        int opt,l,r;
        opt=read(),l=read(),r=read();
        if(opt==1)
            change(1,1,n,l,r);
        if(opt==2)
        {
            Matrix ans=query(1,1,n,l,r);
            printf("%d\n",(ans.a[3][1]+ans.a[3][2])%mod);//最后要加起来
        }
    }
    return 0;
}

总结

随着题目难度的不断增加,对于线段树的合并或维护变得越来越难、越来越复杂,如矩阵乘法,小白逛公园等,所以考试时要想的全面一点。

原文地址:https://www.cnblogs.com/ez-lcw/p/11382995.html

时间: 2024-08-30 00:30:26

【XSY2538】/【HDU6155】Subsequence Count(矩阵乘法+线段树)的相关文章

ZOJ 2671 -Cryptography ( 矩阵乘法 + 线段树 )

ZOJ 2671 - Cryptography ( 矩阵乘法 + 线段树 ) 题意: 给定模数r, 个数n, 询问数m 然后是n个矩阵,每次询问,输出矩阵联乘之后的结果. 分析: 矩阵乘法 + 线段树优化 这里线段树只有询问没有更新操作. PS:顺便仰慕一下watashi.... 代码: #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using names

HDU 5068 Harry And Math Teacher( 矩阵乘法 + 线段树维护 )

HDU 5068 Harry And Math Teacher( 矩阵乘法 + 线段树维护 ) 题意: 首先是这题题意理解错误,,其次是这题无法理解状态... 已经不是英文有多烂的情况了,是中文没学好啊.....大学不学语文才是真正的硬伤啊 题目意思 有一个城堡有很多层楼, 每层楼有2个门,每个门里面又有两个楼梯,可以通往上一层的两个门 问,从x层楼到y层楼有多少中方法(不能返回) 具体看图吧,,,已经不会说话了 1 #include <cstdio> 2 #include <cstri

ZOJ 2671 Cryptography 矩阵乘法+线段树

B - Cryptography Time Limit:5000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Submit Status Practice ZOJ 2671 Description Young cryptoanalyst Georgie is planning to break the new cipher invented by his friend Andie. To do this, he must

POJ 2777 Count Color (线段树区间更新加查询)

Description Chosen Problem Solving and Program design as an optional course, you are required to solve all kinds of problems. Here, we get a new problem. There is a very long board with length L centimeter, L is a positive integer, so we can evenly d

poj 2777 Count Color(线段树区间修改)

题目链接:http://poj.org/problem?id=2777 题目意思:就是问你在询问的区间里有几种不同的颜色 思路:这题和一般的区间修改差不多,但是唯一不同的就是我们要怎么计算有种颜色,所以这时候我们就需要把延时标记赋予不同的意义,当某段区间有多种颜色时就赋值为-1,当为一种颜色时就把它赋值为这个颜色的号数.这儿我们要怎么统计询问区间不同的颜色数叻,为了不重复计算同一种颜色,那么我们就需要用一个数组来标记计算过的颜色,当我们下次遇到时就不需要再次计算了.... 代码核心处就在计数那儿

PKU 2777 Count Color (线段树区间更新)

题意: 给你三个数:L (1 <= L <= 100000), T (1 <= T <= 30) and O (1 <= O <= 100000),表示有一长度为L的板(1~L), 有T种颜色(1~T),然后有O个操作,初始板1~L的颜色为1,"C A B C"表示在区间A,B图上C颜色, "P A B" 表示询问 A,B区间有几种不同的颜色. #include <stdio.h> #include <iostr

poj 2777 Count Color【线段树段更新】

题目:poj 2777 Count Color 题意:给出一段1 * n 的栅栏,有两种操作,第一种:把 l -- r 全部染成同一颜色t,第二种,查询 l---r 一共有多少种颜色. 分类:线段树 分析:我们可以给每个节点加一个标记,标记当前节点是否只有一种颜色,然后对只有一种颜色的节点如果要染色的话,那么他会变成几种颜色的,这时候记得向下更新一次就好,统计的时候统计节点有单个颜色的颜色就好. 代码: #include <cstdio> #include <cstring> #i

[HDU6155]Subsequence Count(线段树+矩阵)

DP式很容易得到,发现是线性递推形式,于是可以矩阵加速.又由于是区间形式,所以用线段树维护. https://www.cnblogs.com/Miracevin/p/9124511.html 关键在于证明区间操作中,可以直接在打标记的位置翻转矩阵两行两列. 上面网址用代数形式证了一遍,这里考虑从矩阵本身解释. 由线代内容可知,将一个矩阵作初等行变换,相当于将其左乘一个作了相应初等列变换的单位矩阵.同理将一个矩阵作初等列变换,相当于将其又乘一个作了相应初等行变换的单位矩阵. 这里,左乘的矩阵$T=

HDU 6155 Subsequence Count(矩阵 + DP + 线段树)题解

题意:01串,操作1:把l r区间的0变1,1变0:操作2:求出l r区间的子序列种数 思路:设DP[i][j]为到i为止以j结尾的种数,假设j为0,那么dp[i][0] = dp[i - 1][1] + dp[i -1][0] (0结尾新串) + dp[i - 1][0] (0结尾旧串) - dp[i - 1][0] (重复) + 1(0本身被重复时去除). 那么可以得到转移时的矩阵 $$ \left( \begin{matrix} dp[i - 1][0] & dp[i - 1][1] &am