HDU6068 Classic Quotation KMP+前缀和

(题解csy说得很详细了,这里复述一下,并补充 si,j sufi,j 的具体求法)

假定我们使用KMP算法,对每个询问暴力求解,设:

prefi 表示 S 的的前缀i T 进行KMP后KMP的指针到达了哪里。

pregi表示 S 的前缀i T 出现的次数。

sufi,j表示从 S 的后缀i,从失配指针 j 开始KMP,能匹配多少T

那么前缀 i 和后缀j拼起来后,T的个数为 pregi+sufj,prefi

那么对于询问 L R的答案为:

ans=i=1Lj=Rn(pregi+sufj,prefi) .

但是复杂度太高了,我们发现 pregi j 并无关系,所以我们可以利用前缀和O(1)求得 i=1Lj=Rnpregi

同样地,我们思考能不能用后缀和维护 sufj,prefi ,思考一下可以发现它只与 prefi 的值有关,我们并不需要知道他的i到底是多少,而 prefi 的值域是 [0,m) ,所以我们可以算出每个前缀中,每个适配指针有多少个,我们就可以 O(m) 地求到 i=1Lj=Rn(sufj,prefi

preg suf 表示 preg 的前缀和 suf 的后缀, si,j 表示前 i 个中pref j 的个数,对答案ans我们就有了新的表示:

ans=(nR+1)pregL+i=0m1(sL,i×sufR,i)

新的问题是怎么求的 s suf

s 在对S KMP 时就可以轻松求出。对于未做后缀和的 suf ,因为字符集为 26 ,所以我们可以对每个失配指针 i 去枚举假设碰到字母j后,是否匹配和新的失配指针并记录到 nxt2 数组中,这样我们可以类似dp的算出 suf suf[i][j]=isMatch[j][a[i]]+suf[i+1][nxt2[j][a[i]]]

(kmp板子同样来自csy)

#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 5e4 + 10;
const int maxm = 110;

int nxt[maxn];
char x[maxn],y[maxm];
int n,m,q;
int suf[maxn][maxm];
int s[maxn][maxm];
int pre[maxn];
int l,r;
int nxt2[maxm][200];
int ismatch[maxm][200];


int main()
{
//  freopen("1.in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&q);
        scanf("%s%s",x+1,y+1);
        long long ans = 0;
        int i,j;
        for(nxt[1] = j = 0, i = 2; i <= m; nxt[i++] = j)
        {
            while(j && y[j+1] != y[i])
                j = nxt[j];
            if(y[j+1] == y[i])
                j++;
        }
        for(j = 0,i = 1; i <= n; i++)
        {
            while(j && y[j+1] != x[i])
                j = nxt[j];
            if(y[j+1] == x[i])
                j++;
            pre[i] = pre[i - 1];
            if(j == m)
                pre[i]++, j = nxt[j];   
            for(int k = 0; k < m; k++)
                s[i][k] = s[i-1][k];
            s[i][j]++;
        }
        for(int i = 1; i <= n; i++)
            pre[i] += pre[i-1];
        for(int i = 0; i < m; i++)
            for(int j = 'a'; j <= 'z'; j++)
            {
                ismatch[i][j] = 0;
                int k = i;
                while(k && y[k+1] != j)
                    k = nxt[k];
                if(y[k+1] == j)
                    k++;
                if(k == m)
                    k = nxt[k],ismatch[i][j] = 1;
                nxt2[i][j] = k;
            }
        for(int i = 0; i < m; i++)
            suf[n+1][i] = 0;
        for(int i = n; i >= 1; i--)
            for(int j = 0; j < m; j++)
                suf[i][j] = ismatch[j][x[i]] + suf[i+1][nxt2[j][x[i]]];
        for(int i = n; i >= 1; i--)
            for(int j = 0; j < m; j++)
                suf[i][j] += suf[i+1][j];
        while(q--)
        {
            scanf("%d%d",&l,&r);
            ans = 1ll * (n - r + 1) * pre[l];
            for(int i = 0; i < m; i++)
                ans += 1ll * s[l][i] * suf[r][i];
            printf("%lld\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值