LibreOJ #6074.「2017 山东一轮集训 Day6」子序列 dp+矩阵乘法

题意

给一个长度为n的字符串,有q次询问,每次询问[l,r]构成的字符串有多少个本质不同的子序列。
n,q105 n , q ≤ 10 5 ,字符集大小为9.

分析

先考虑如何暴力求区间内本质不同的子序列数。
我们可以每个位置向他往后第一次出现的那些字符连边,然后建一个虚点往区间从左往右第一次出现的字符连边,那么显然从虚点开始走的路径数就是答案。
fi,j f i , j 表示以区间内的第一个字符 i i 为开头,在上述DAG上走,走到位置j的方案。
考虑当 j j 往左移一位后dp数组会发生什么变化。
不难发现fsj1,j1=1,对于其余的 j j ,有fsj1,j=i=08fi,j
我们设 ansi=rj=lfi,j a n s i = ∑ j = l r f i , j ,那么每次 j j 左移一位可以看成anssj1=1+j=08ans[j]
最后 8i=0ansi ∑ i = 0 8 a n s i 即为答案。
我们可以把每次 ans a n s 的变化写成一个10*10的矩阵,那么我们要求的就是 alal+1...ar a l ∗ a l + 1 ∗ . . . ∗ a r
这样的话就只用预处理出矩阵的前缀和还有逆矩阵的前缀和,就可以直接得到答案了。
注意在求答案的时候可以用向量乘矩阵,还有在矩阵求逆的时候,会发现这个矩阵有特殊的性质,可以直接 O(n2) O ( n 2 ) 求逆。
时间复杂度 O(nc3+qc2) O ( n c 3 + q c 2 ) ,其中 c c <script type="math/tex" id="MathJax-Element-2307">c</script>是字符集大小。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=100005;
const int MOD=1000000007;

int n,ans[10],tmp[10];
char str[N];
struct Matrix
{
    int a[10][10];

    Matrix operator * (const Matrix &b) const
    {
        Matrix c;
        memset(c.a,0,sizeof(c.a));
        for (int i=0;i<=9;i++)
            for (int k=0;k<=9;k++)
                for (int j=0;j<=9;j++)
                    (c.a[i][j]+=(LL)a[i][k]*b.a[k][j]%MOD)%=MOD;
        return c;
    }

    Matrix get_inv(int x)
    {
        Matrix b;
        memset(b.a,0,sizeof(b.a));
        for (int i=0;i<=9;i++) b.a[i][i]=1;
        for (int i=0;i<=9;i++)
            if (i!=x) b.a[i][x]=MOD-1;
        return b;
    }
}ma[10],inv[10],a[N],b[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void pre()
{
    for (int i=0;i<9;i++)
    {
        for (int j=0;j<=9;j++) ma[i].a[j][j]=1;
        for (int j=0;j<=9;j++) ma[i].a[j][i]=1;
        inv[i]=ma[i].get_inv(i);
    }
    for (int i=0;i<=9;i++) a[0].a[i][i]=b[0].a[i][i]=1;
    for (int i=1;i<=n;i++)
    {
        a[i]=a[i-1]*ma[str[i]-'a'];
        b[i]=inv[str[i]-'a']*b[i-1];
    }
}

int main()
{
    scanf("%s",str+1);
    n=strlen(str+1);
    pre();
    int q=read();
    while (q--)
    {
        int l=read(),r=read(),sum=0;
        for (int i=0;i<=9;i++) ans[i]=0;
        ans[9]=1;
        for (int i=0;i<=9;i++)
        {
            int s=0;
            for (int j=0;j<=9;j++)
                (s+=(LL)ans[j]*b[l-1].a[j][i]%MOD)%=MOD;
            tmp[i]=s;
        }
        for (int i=0;i<9;i++)
        {
            int s=0;
            for (int j=0;j<=9;j++)
                (s+=(LL)tmp[j]*a[r].a[j][i]%MOD)%=MOD;
            (sum+=s)%=MOD;
        }
        printf("%d\n",sum);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值