BZOJ 3555: [Ctsc2014]企鹅QQ

本文介绍了一种不使用Hash的字符串匹配算法,利用反向字符串建立Trie树来高效处理多个字符串的后缀,通过分治策略解决前缀相同的问题,适用于字符集较大的场景。

似乎大家全部都用的是hash?那我讲一个不用hash的做法吧。

首先考虑只有一位不同的是哪一位,那么这一位前面的位上的字符一定是全部相同,后面的字符也是全部相同。首先考虑后面的字符。

我们对n个串的反串建trie树,这样,每一个后缀就对应一个trie树上的唯一一个节点,不同的后缀对应的就是不同的节点,这样就不用hash表了。

但是字符集很大,用trie空间太大,那么就用邻接表或map存边就好了。

然后就是令前缀全部相同。那么我们从左到右枚举每一位,按照当前位的字符进行分治,当前位不同的就通过后缀计算贡献,相同的就放在一起,继续分治即可。

然后就跑的飞快,目前是BZOJ rk2,Luogu rk1。

实现的时候有很多细节,具体请参考代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cctype>
#define qmin(x,y) (x=min(x,y))
#define qmax(x,y) (x=max(x,y))
#define vi vector<int>
#define vit vector<int>::iterator
#define pir pair<int,int>
#define fr first
#define sc second
#define mp(x,y) make_pair(x,y)
#define rsort(x,y) sort(x,y),reverse(x,y)
using namespace std;

inline char gc() {
//  static char buf[100000],*p1,*p2;
//  return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
    return getchar();
}

template<class T>
int read(T &ans) {
    ans=0;char ch=gc();T f=1;
    while(!isdigit(ch)) {
        if(ch==EOF) return -1;
        if(ch=='-') f=-1;
        ch=gc();
    }
    while(isdigit(ch))
        ans=ans*10+ch-'0',ch=gc();
    ans*=f;return 1;
}

template<class T1,class T2>
int read(T1 &a,T2 &b) {
    return read(a)!=EOF&&read(b)!=EOF?2:EOF;
}

template<class T1,class T2,class T3>
int read(T1 &a,T2 &b,T3 &c) {
    return read(a,b)!=EOF&&read(c)!=EOF?3:EOF;
}

typedef long long ll;
const int Maxn=6100000;
const int Maxm=31000;
const int Maxl=210;
const int inf=0x3f3f3f3f;

int to[Maxn],nxt[Maxn],first[Maxn],tot=1;
char w[Maxn],s[Maxm][Maxl];
int p[Maxm][Maxl],n,len,x,po[Maxl][Maxl],qq[Maxl],bj[Maxl],ans;
int a[Maxm],siz[Maxl],b[Maxm],tn[Maxn],cnt=1;
queue<int> q[Maxl];

inline void add(int u,int v,char wi) {
    to[tot]=v;
    nxt[tot]=first[u];
    w[tot]=wi;
    first[u]=tot++;
}

void solve(int l,int r,int cur) {
    if(l>r) return ;
    if(cur==len) return ;
    int cnt=0;
    for(int i=l;i<=r;i++) {
        int x=s[a[i]][cur];
        q[x].push(a[i]);
        siz[x]++;
        if(!bj[x]) qq[++cnt]=x,bj[x]=1;
    }
    if(cnt==1) {
        for(int i=1;i<=cnt;i++) bj[qq[i]]=siz[qq[i]]=0;
        while(!q[qq[1]].empty()) q[qq[1]].pop();
        solve(l,r,cur+1);
    }
    else {
        int sxz,zhy=0;
        for(int i=1;i<=cnt;i++) {
            if(siz[qq[i]]>zhy) {
                sxz=i;
                zhy=siz[qq[i]];
            }
        }
        swap(qq[cnt],qq[sxz]);
        int las=l;po[cur][0]=las;
        for(int i=1;i<cnt;i++) {
            int x=qq[i],num=0;
            while(!q[x].empty()) {
                int now=q[x].front();
                q[x].pop();
                b[++num]=p[now][cur+1];
                ans+=tn[b[num]];
                a[las++]=now;
            }
            while(num) tn[b[num--]]++;
            po[cur][i]=las;
        }
        while(!q[qq[cnt]].empty()) {
            int now=q[qq[cnt]].front();
            q[qq[cnt]].pop();
            ans+=tn[p[now][cur+1]];
            a[las++]=now;
        } po[cur][cnt]=las;
        for(int i=l;i<po[cur][cnt-1];i++) tn[p[a[i]][cur+1]]--;
        for(int i=1;i<=cnt;i++) bj[qq[i]]=siz[qq[i]]=0;
        for(int i=1;i<=cnt;i++)
            solve(po[cur][i-1],po[cur][i]-1,cur+1);
    }
}

signed main() {
//  freopen("test.in","r",stdin);
    read(n,len,x);
    for(int i=1;i<=n;i++) {
        scanf("%s",s[i]);
        int now=1;
        for(int j=len-1;j>=0;j--) {
            int temp=0;
            for(int k=first[now];k;k=nxt[k])
                if(w[k]==s[i][j]) {
                    temp=to[k];
                    break;
                }
            if(!temp) add(now,++cnt,s[i][j]),temp=cnt;
            now=temp;
            p[i][j]=now;
        }
    }
    for(int i=1;i<=n;i++) a[i]=i;
    solve(1,n,0);
    printf("%d\n",ans);
    return 0;
}


转载于:https://www.cnblogs.com/shanxieng/p/10783697.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值