【XSY2538】Subsequence Count(线段树+DP+字符串)
\(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 的询问,输出一个整数表示答案。由于答案可能很大,请将答案对 109+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≤105,Q≤10
对于另外30%的数据, 1≤N≤105,1≤Q≤105,type=2
对于100%的数据, 1≤N≤105,1≤Q≤105
题解
首先,我们先考虑如何实现询问多少个不同的子序列。
我们设一个\(dp[i][2]\)数组,表示到第\(i\)位,以0/1结尾的子串的个数
其实它的状态转移方程十分好想
1.若\(s_{i}=0\)
\(dp[i][0]=dp[i-1][1]+dp[i-1][0]+1\)
\(dp[i][1]=dp[i-1][1]\)
2.若\(s_{i}=1\)
\(dp[i][0]=dp[i-1][0]\)
\(dp[i][1]=dp[i-1][0]+dp[i-1][1]+1\)
那么,我们可以通过列出一个矩阵进行矩阵乘法来实现这个\(dp\)
1.若\(s_{i}=0\)
\(\begin{bmatrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ dp[i-1][0] & dp[i-1][1] & 1\\ \end{bmatrix}\)\(\times\)\(\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ 1 & 0 & 1\\ \end{bmatrix}\)\(=\)\(\begin{bmatrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ dp[i][0] & dp[i][1] & 1\\ \end{bmatrix}\)
2.若\(s_{i}=1\)
\(\begin{bmatrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ dp[i-1][0] & dp[i-1][1] & 1\\ \end{bmatrix}\)\(\times\)\(\begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 1 & 1\\ \end{bmatrix}\)\(=\)\(\begin{bmatrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ dp[i][0] & dp[i][1] & 1\\ \end{bmatrix}\)
这样就可以实现我们的\(dp\)转移方程,最后输出答案时输出最后得到的矩阵的\(a[3][1]+a[3][2]\)即可。
但是怎么维护区间询问呢?
我们联系上下文 的修改,发现也是区间取反,于是我们选择使用线段树
我们开一棵线段树,每个节点维护一个对应的矩阵
现在我们考虑如何将区间内的数0,1取反
我们发现,取反操作可以用交换当前节点的单位矩阵实现,把1矩阵交换为0,0换为1。
但是,如果直接改变有些麻烦,我们通过观察发现,其实直接将矩阵的1,2行交换,1,2列交换,就可以实现矩阵交换
如:\(\begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 1 & 1\\ \end{bmatrix}\)\(\Rightarrow\)\(\begin{bmatrix} 0 & 1 & 0 \\ 1 & 1 & 0 \\ 0 & 1 & 1\\ \end{bmatrix}\)\(\Rightarrow\)\(\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ 1 & 0 & 1\\ \end{bmatrix}\)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100010,mod=1e9+7;
int n;
char ch[N];
int rev[N<<2];
struct matric
{
int a[4][4];
}t[N<<2],zero,one,none;
matric operator*(matric a,matric b)//矩阵乘法
{
matric 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;
}
void pushup(int k)
{
t[k]=t[k<<1]*t[k<<1|1];
}
void build(int k,int l,int r)//线段树
{
if(l==r)
{
if(ch[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);
pushup(k);
}
void turn(int k)//矩阵取反
{
for(int i=1;i<=3;i++)swap(t[k].a[i][1],t[k].a[i][2]);
for(int i=1;i<=3;i++)swap(t[k].a[1][i],t[k].a[2][i]);
rev[k]^=1;
}
void pushdown(int k)
{
if(rev[k])
{
turn(k<<1);
turn(k<<1|1);
rev[k]=0;
}
}
void reverse(int k,int l,int r,int x,int y)
{
if(x<=l&&r<=y)
{
turn(k);
return ;
}
pushdown(k);
int mid=(l+r)>>1;
if(x<=mid)reverse(k<<1,l,mid,x,y);
if(y>mid)reverse(k<<1|1,mid+1,r,x,y);
pushup(k);
}
void init()//初始化单位矩阵
{
one.a[1][1]=one.a[1][2]=one.a[2][2]=one.a[3][2]=one.a[3][3]=1;
zero.a[1][1]=zero.a[2][1]=zero.a[2][2]=zero.a[3][1]=zero.a[3][3]=1;
}
matric query(int k,int l,int r,int x,int y)
{
if(x<=l&&r<=y)return t[k];
pushdown(k);
int mid=(l+r)>>1;
if(y<=mid)return query(k<<1,l,mid,x,y);
else
{
if(x>mid)return query(k<<1|1,mid+1,r,x,y);
else return query(k<<1,l,mid,x,y)*query(k<<1|1,mid+1,r,x,y);
}
}
int main()
{
int q;
init();
scanf("%d %d",&n,&q);
scanf("%s",ch+1);
int op,l,r;
build(1,1,n);
for(int i=1;i<=q;i++)
{
scanf("%d %d %d",&op,&l,&r);
if(op==1)reverse(1,1,n,l,r);
else
{
matric ans=query(1,1,n,l,r);
printf("%d\n",(ans.a[3][1]+ans.a[3][2])%mod);
}
}
return 0;
}