[计蒜客16956] Query on a string [2017 ACM-ICPC 亚洲区(乌鲁木齐赛区)网络赛 G]

题意

给定字符串 S T
两种操作:
1. 修改 S 串某个位置的字符
2. 询问S某子串中出现了多少次 T
|T|10

题解

如果没有修改,我们可以对 T fail指针,然后在 S 中kmp一遍并记录哪些位置匹配了完整的T串,这相当于一个01数组,询问则是对这个01数组求区间和,可以简单地用树状数组实现。这样一次询问就是 O(logn) 的。
当修改某个字符时,01数组中受到影响的显然最多是10个位置,即每次修改操作暴力kmp并修改对应位置的01数组即可。这样一次修改就是 O(logn) 的,常数为10左右。于是整体复杂度 O(qlogn)

代码

#include <bits/stdc++.h>

#define  kN  100010LL
#define  kL  100010LL
#define  lb  (p&(-p))

int q,n,m,fail[20],C[kL];
bool ok[kL];
char s[kL],t[20];

inline void ins(int p,int w){
    for(;p<=n;p+=lb)C[p]+=w;
}
inline int sum(int p) {
    int ret=0;for(;p>0;p-=lb)ret+=C[p];return ret;
}

inline void getfail() {
    int i, p = 0;
    fail[1] = 0;
    for(i=2;i<=m;i++) {
        while(p&&t[p+1]!=t[i])p=fail[p];
        p+=(t[p+1]==t[i]), fail[i] = p;
    }
}

inline void Query(int l,int r) {
    l += m-1;
    if (l<=r) printf("%d\n",sum(r)-sum(l-1));
    else puts("0");
}

inline void Change(int p,char c) {
    if (c == s[p]) return;
    s[p] = c;
    int l = p-m+1, r = p+m-1, i;
    if (l<1) l = 1;
    if (r > n) r = n;
    int pos = p;
    p = 0;
    for(i = l;i <= r;i ++ ) {
        while(p && t[p+1]!=s[i]) p = fail[p];
        p += (t[p+1]==s[i]);
        if (i >= pos) {
            if (p == m) {
                if (!ok[i]) ok[i]=true,ins(i,1);
            } else {
                if (ok[i]) ok[i] = false, ins(i,-1);
            }
        }
    }
}

inline void work() {
    int p, i, j, k;
    char mode, c;
    scanf("%d%s%s",&q,s+1,t+1);
    n=strlen(s+1);
    m=strlen(t+1);
    getfail();
    memset(ok,0,sizeof ok);
    memset(C,0,sizeof C);
    p = 0;
    for(i=1;i<=n;i++) {
        while(p && t[p+1]!=s[i]) p = fail[p];
        p += (t[p+1]==s[i]);
        if (p == m) ok[i]=true,ins(i,1);
    }
    while(q-->0) {
        while(mode=getchar(),mode!='Q'&&mode!='C');
        if (mode == 'Q') {
            scanf("%d%d",&j,&k);
            Query(j,k);
        } else {
            scanf("%d",&j);
            while(c=getchar(),c<'!');
            Change(j,c);
        }
    }
    puts("");
}

int main() {
    int T;
    scanf("%d",&T);
    while(T-->0) work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值