LOJ子序列
题目描述
题解
对于子序列的dp,我们可以设置一个dp。
我们设dp[i]表示以i这个字符结尾的子序列个数,转移为dp[i]+=∑dp[k]-dp[i]。其实我们发现这样等价于dp[i]=∑dp[k]。
另外我们还要再设一个空字符的位置表示空字符,所以我们的转移矩阵长成这样。
1 0 1 0 0
0 1 1 0 0
0 0 1 0 0
0 0 1 1 0
0 0 1 0 1
我们一开始是空的,所以转移矩阵为
0 0 0 0 1
最后我们要归为一个答案,所以还要乘上一个列矩阵。
1
1
1
1
0
直接做是N^3的,我们发现这个矩阵长得非常有特点,我们把它的逆矩阵求出来之后发现。
1 0 -1 0 0
0 1 -1 0 0
0 0 1 0 0
0 0 -1 1 0
0 0 -1 0 1
所以我们的思路就是让行向量*1~l-1的逆矩阵再乘上1~r的正矩阵,在乘上列矩阵。
对于正矩阵和逆矩阵的部分我们可以分开维护。
对于正矩阵的右乘的过程,我们可以维护每一行的和,因为每次的乘法相当于把ch位变成这一行的和,所以就直接维护就好了。
注意我们维护的是最后做完之后再乘上一个列矩阵,所以维护的时候只需要维护矩阵和矩阵乘列向量的值就好了。
对于逆矩阵的左乘,我们考虑它实际上是把当前列的第ch位减掉,而且它是对每个列除了ch行都批量的减掉,所以我们维护一下每一列的加法标记就好了。
同样我们也是要维护乘完之后再去乘上行向量的答案。
代码
#include<iostream> #include<cstdio> #include<cstring> #define M 11 #define N 100002 using namespace std; const int mod=1e9+7; char s[N]; int sum[M],n,c,now[M][M],val[N],nowl[N][M],nowr[N][M],tag[M]; inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } int main(){ scanf("%s",s+1);n=strlen(s+1); c=10; for(int i=0;i<=c;++i)sum[i]=1,nowr[0][i]=1,now[i][i]=1; nowr[0][c]=0; for(int i=1;i<=n;++i){ int x=s[i]-'a'; for(int j=0;j<=c;++j) val[j]=now[j][x],now[j][x]=sum[j]; for(int j=0;j<=c;++j)sum[j]=(1ll*sum[j]*2-val[j]+mod)%mod; for(int j=0;j<=c;++j)nowr[i][j]=(1ll*sum[j]-now[j][c]+mod)%mod; } nowl[0][c]=1; memset(now,0,sizeof(now)); for(int i=0;i<=c;++i)now[i][i]=1; for(int i=1;i<=n;++i){ int x=s[i]-'a'; for(int j=0;j<=c;++j)val[j]=(now[x][j]+tag[j])%mod; for(int j=0;j<=c;++j)now[x][j]=(now[x][j]+val[j])%mod,tag[j]=(tag[j]-val[j]+mod)%mod; for(int j=0;j<=c;++j)nowl[i][j]=(now[c][j]+tag[j])%mod; } int q=rd();int l,r; while(q--){ l=rd();r=rd();int ans=0; for(int i=0;i<=c;++i)(ans+=1ll*nowr[r][i]*nowl[l-1][i]%mod)%=mod; printf("%d\n",ans); } return 0; }