字符串专题

本文解析了四个算法挑战赛题目:平衡特征、敏感词过滤、净网行动及子串计数。涉及后缀数组、AC自动机等高级算法,提供源代码实现。

平衡特征(feature.in/feature.out)

有n只青蛙排成一排,我们将青蛙的不同之处归纳为K种特征,比如第1种特征表示它是雌性还是雄性,2号特征代表它是不是青蛙中的长者等。
我们将每只青蛙的特征用一个标识符表示,数的二进制表示的第i位,表示第i种特征,比如一只青蛙的标识符是13,二进制位1101,则它有1,3,4号特征。
一个平衡的区间是指,选出这一排青蛙中连续的一段,在区间中每种特征出现的次数是一样的。请问这样平衡区间的最大长度是多少?
【输入格式】
第一行两个数n,k,表示青蛙的个数和特征的个数。
接下来n行,每行一个数,代表青蛙的标识符。
【输出格式】
一个整数,表示平衡区间的最大长度。

【输入样例】
7 3
7
6
7
2
1
4
2
【输出样例】
4

【样例解释】
[3,6]这个区间,每种特征出现的次数均为2。
【数据规模】
30% 数据满足 n≤1000。
100% 数据满足 n≤100000,1≤K≤30。

题解

首先将所有青蛙的特征值求前缀和:

010000
100101
010101
011010
101010
得到
010000
110101
120202
131212
232222
这样我们只需要枚举l 和r,将前缀和相减,如果得到一个222222/111111 这
样相等的就可以了。但是这样复杂度是O(Kn^2)的。
我们考虑将前缀和处理一下,所有特征统一减去第一种特征:
010000
110101
120202
131212
232222
得到
010000
00(-1)0(-1)0
01(-1)1(-1)1
020101
010000
在这个处理后的串中,l 和r 的值相等,当且仅当从l+1 到r 的区间满足条
件。所以原题转化为找出相等的串,Hash 即能在O(Kn)内出结果。

code

#include <cstdio>
#include <string>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
struct node{
    int v;
    node *next;
}hs[100005];
int n,k,x,h,ans,sum[100005][31],c[100005][31];
bool cmp(int a1,int a2){
    for(int i=0;i<k;i++)if(c[a1][i]!=c[a2][i])return 0;
    return 1;
}
int main(){
    freopen("feature.in", "r", stdin);
    freopen("feature.out", "w", stdout);
    scanf("%d%d",&n,&k),memset(hs,0,sizeof(hs));
    for(int i=0;i<99991;i++) hs[i].next=NULL;
    memset(sum,0,sizeof(sum)),memset(c,0,sizeof(c));
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        for(int j=0;j<k;j++){
            if(((x>>j)&1)==0)sum[i][j]=sum[i-1][j];
            else sum[i][j]=sum[i-1][j]+1;
        }
    }
    for(int i=0;i<=n;i++){
        h=0;
        for(int j=0;j<k;j++){
            c[i][j]=sum[i][j]-sum[i][0];
            h=(1LL*c[i][j]*+h+70000523LL)%99991;//这样hash我都能过。。7王523、142857等等dou~ko~yi~
        }
        if(hs[h].next!=NULL){
            node *p=hs[h].next;
            while(p!=NULL){
                if(cmp(i,p->v))ans=max(ans,i-p->v);
                p=p->next;
            }
        }
        node *p=new(node);
        p->v=i;
        p->next=hs[h].next;
        hs[h].next=p;
    }
    printf("%d\n",ans);
    return 0;
}

敏感词(sensitive.in/sensitive.out)

给出一个敏感词w,和一篇文章p,你的工作很简单,每次找出p中第一次出现的w,然后将这个w删掉,然后重复这个过程,直到在p中找不到w为止。请你输出最后删减后的文章p。
【输入格式】
第一行一个词w。
第二行一篇文章p。
【输出格式】
输出一行,删减过的文章。

【输入样例】
abc
aaabcbc
【输出样例】
a

【数据规模】
20% 数据满足1≤len(p), len(w)≤1000。
100% 数据满足1≤len(p), len(w)≤300000。
所有字符串均只包括小写字母。

题解

kmp+栈或双向链表。
按照题目要求,很容易想出一个算法,每次做kmp,找到第一次匹配的位置,
然后删掉这个匹配,再做kmp。。直到没有匹配为止。删除的时候采取暴力删
除的方法,总复杂度是O(len^2)的,可以获得20 分。
可以用一个栈来优化这个过程:
从头开始扫描文章,扫描到的每一个字符压入栈,并记录每一个字符匹配到的
位置。当发现一个匹配的时候,将匹配的字符出栈。然后从栈顶元素匹配到的
位置继续匹配。最后栈中保存的就是结果。
也可以用双向链表来优化这个过程:
和栈优化的方法类似,只是使用双向链表优化删除匹配串,删除操作变为简单
改变匹配位置指针的值。
用栈或者双向链表优化后的复杂度是O(len),可以获得100 分。

code

//水水水。。。。。话说string类型函数的处理复杂太高了吧,什么优化都没有。。。
#include<cstdio>
#include<cstring>
#define M 5000005
char tmp[M],str[M],ans[M];
int fail[M],pre[M],k,j;
void getnext(char* tmp){
    int len=strlen(tmp);
    for(register int i=1;i<len;i++){
        int j=fail[i];
        while(j&&tmp[j]!=tmp[i])j=fail[j];
        fail[i+1]=tmp[i]==tmp[j]?j+1:0;
    }
}
int main(){
    freopen("sensitive.in","r",stdin);
    freopen("sensitive.out","w",stdout);
    scanf("%s%s",&tmp,&str),getnext(tmp);
    int m=strlen(tmp),n=strlen(str);
    for(register int i=0;i<n;i++,k++){
        while(j&&tmp[j]!=str[i])j=fail[j];
        if(ans[k]=str[i],tmp[j]==str[i])j++;
        if(j==m)k-=m,j=pre[k];
        pre[k]=j,ans[k+1]=0;
    }
    puts(ans);
    return 0;
}

净网行动(cleanse.in/cleanse.out)

在网络上,尤其是网络游戏中,一些人出口成脏,影响网络环境。现在给
出n 个关键词Ti,和一篇待净化的文章P,请将P 中的关键词全部用*号省略表
示。
【输入格式】
第一行一个数n,表示关键词的个数。
接下来n 行,每行一个字符串,表示关键词Ti。
接下来一行,表示文章P
【输出格式】
一共n 行,第i 行表示第i 组的s 个意思个数。

【输入样例】
3
trump
ri
o
Donald John Trump (born June 14, 1946) is an American businessman,
television personality, author, politician, and the Republican
Party nominee for President of the United States in the 2016
election. He is chairman of The Trump Organization, which is the
principal holding company for his real estate ventures and other
business interests.
【输出样例】
D*nald J*hn * (b*rn June 14, 1946) is an Ame**can businessman,
televisi*n pers*nality, auth*r, p*litician, and the Republican
Party n*minee f*r President *f the United States in the 2016
electi*n. He is chairman f The *rganizati*n, which is the
p**ncipal h*lding c*mpany f*r his real estate ventures and *ther
business interests.

【数据规模】
设sum(t)为所有关键词长度之和。
20% 数据满足 len(t),sum(t),len(p),n≤ 1000。
100% 数据满足len(t),sum(t),len(p),n ≤ 300000。
关键词只包括小写字母,不区分大小写。

题解

AC 自动机
比较裸的AC 自动机,模型就是在AC 自动机中找字典匹配。这道题另需注意
的是,需要记录好串长和匹配位置,以正确处理*号。注意输入可能带有空格和
特殊字符。

code

#include <bits/stdc++.h>
using namespace std;
const int maxnode = 1000005;
const int sigma_size = 26;
struct Aho_Corasick{
    int child[maxnode][sigma_size], len[maxnode];
    int f[maxnode], last[maxnode], cnt[maxnode];
    bool value[maxnode];
    int size;
    void init(){
        size = 1;
        memset(child[0], 0, sizeof(child[0]));
        memset(value, false, sizeof(value));
        memset(cnt, 0, sizeof(cnt));
    }
    void insert(char * str){
        int pos = 0;
        for (int i=0; str[i]; ++i){
            int id = str[i]-'a';
            if (!child[pos][id]){
                memset(child[size], 0, sizeof(child[size]));
                child[pos][id] = size++;
            }
            pos = child[pos][id];
        }
        value[pos] = true;
        len[pos] = strlen(str);
    }
    void get_fail(){
        queue<int> q;
        f[0] = 0;
        for (int i=0; i<sigma_size; ++i){
            int u = child[0][i];
            if (u){
                f[u] = 0;
                q.push(u);
                last[u] = 0;
            }
        }
        while (!q.empty()){
            int r = q.front();
            q.pop();
            for (int i=0; i<sigma_size; ++i){
                int u = child[r][i];
                if (!u){
                    child[r][i] = child[f[r]][i];
                    continue;
                }
                q.push(u);
                int v = f[r];
                while (v && !child[v][i])
                    v = f[v];
                f[u] = child[v][i];
                last[u] = value[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void find(char * str){
        int pos = 0;
        for (int i=0; str[i]; ++i){
            if (!isalpha(str[i]))
                continue;
            int id = tolower(str[i])-'a';
            pos = child[pos][id];
            if (value[pos])
                count(pos, i);
            else if (last[pos])
                count(last[pos], i);
        }
    }
    void count(int pos, int id){
        if (pos){
            ++cnt[id+1];
            --cnt[id-len[pos]+1];
            count(last[pos], id);
        }
    }
} ac;
char str[maxnode];
int n;
int main(){
    scanf("%d", &n);
    ac.init();
    for (int i=0; i<n; ++i){
        scanf("%s", str);
        ac.insert(str);
    }
    ac.get_fail();
    getchar();
    fgets(str, maxnode, stdin);
    ac.find(str);
    int ans = 0;
    for (int i=0; str[i]; ++i){
        ans += ac.cnt[i];
        if (ans < 0)putchar('*');
        else putchar(str[i]);
    }
    return 0;
}

子串个数(substring.in/substring.out)

给出一个串S,求S 中包含字母a 的不同的子串有多少个?
【输入格式】
第一行一个串,表示S。
【输出格式】
一个整数,表示包含字母a 的不同的子串的个数。
【输入样例1】
abc
【输出样例1】
3
【输入样例2】
aaa
【输出样例2】
3
【数据规模】
30% 数据满足len(s)≤1000。
100% 数据满足len(s)≤100000。
S 只包含小写字母。

 题解

后缀数组
是论文题目“统计不同的子串个数”的变式题目。
考虑在统计sa[i]的子串个数时:
设从sa[i]开始的后缀长度为len。
设height[i] = sa[i]和sa[i-1]的最长公共子串长度 = h。
设sa[i]之后,离sa[i]位置最近的字符a 距离sa[i]的距离为k。
则若h>=k 则ans += len – h;(从sa[i]开始的后缀的不同前缀都包含了a 字符)
若k>h 则ans+=len-k (只考虑包含了a 字符的不同前缀)
所以预处理出每个位置的k,最后使用longlong 的ans 统计答案即可。

code

写不出来了,只好贴代码咯~~

#include <cstdio>
#include <cstring>
#include <algorithm>
const int N=100010;
const int INF=14285714;
int r[N],rnk[N],lcp[N],tsa[N],b[N],wx[N],sa[N],xx[N],n,m;
char s[N];
long long ans;
bool cmp(int *r,int a,int b,int l){
    return r[a]==r[b] && r[a+l]==r[b+l];
}
void cntlcp(){
    for(int j,k=0,i=0;i<n;lcp[rnk[i++]]=k)
        for(k?k--:k,j=sa[rnk[i]-1];r[i+k]==r[j+k];k++);
}
void da(){
    int i,j,p,*x=wx,*y=tsa,*t;
    for(i=0;i<m;i++)b[i]=0;
    for(i=0;i<n;i++)b[x[i]=r[i]]++;
    for(i=1;i<m;i++)b[i]+=b[i-1];
    for(i=n-1;i>=0;i--) sa[--b[x[i]]]=i;
    for(j=1,p=1;p<n;j*=2,m=p){
        for(p=0,i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<m;i++)b[i]=0;
        for(i=0;i<n;i++)b[x[i]]++;
        for(i=1;i<m;i++)b[i]+=b[i-1];
        for(i=n-1;i>=0;i--) sa[--b[x[y[i]]]]=y[i];
        t=x;x=y;y=t;x[sa[0]]=0;
        for(i=1,p=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
    }
    for(register int i=0;i<n;i++)rnk[i]=x[i];
}
int main(){
    freopen("substring.in","r",stdin);
    freopen("substring.out","w",stdout);
    char ch='a';
    scanf("%s",s);
    int len=strlen(s),pre=2*INF;
    for(register int i=len-1;i>=0;i--){
        if(s[i]==ch)pre=i;
        xx[i]=pre-i;
    }
    n=len+1;m=27;
    for(register int i=0;i<len;i++)r[i]=s[i]-'a'+1;
    r[len]=0;
    da(),cntlcp();
    if(xx[sa[1]]<INF)ans=len-sa[1]-xx[sa[1]];
    for(register int i=2;i<=len;i++){
        if(xx[sa[i]]>INF)continue;
        if(xx[sa[i]]<lcp[i])ans+=len-sa[i]-lcp[i];
        else ans+=len-sa[i]-xx[sa[i]];
    }
    printf("%I64d",ans);
    return 0;
}

当然。。后缀自动机也是极好的!!!

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
struct Trie{
    int len,dag;
    long long cnt,cnta;
    Trie *next[26],*fail;
    Trie(int len=0):fail(NULL),len(len),cnt(0),dag(0),cnta(0){
        memset(next,0,sizeof(next));
    }
};
struct SAM{
    Trie *pool,*top,*root,*last;
    Trie* newnode(int len) {
        top->len=len;
        return top++;
    }
    SAM(){}
    SAM(int n) {
        pool=new Trie[(2*n+5)];
        top=pool;
        root=top++;
        last=root;
    }
    inline void extends(char ch) {
        int c=ch-'a';
        Trie *node=newnode(last->len+1),*f=last;
        while(f&&f->next[c]==NULL)f->next[c]=node,f=f->fail;
        if(!f)node->fail=root;
        else{
            Trie *p=f->next[c];
            if(p->len==f->len+1)node->fail=p;
            else {
                Trie *q=newnode(f->len+1);
                memcpy(q->next,p->next,sizeof(q->next)); 
                q->fail=p->fail;
                p->fail=q;
                node->fail=q;
                while(f&&f->next[c]==p)f->next[c]=q,f=f->fail;
            }
        }
        last=last->next[c];
    }
};
long long res;
int n;
char s[100005];
SAM sam;
void DAG(){
    for(Trie* p=sam.pool;p!=sam.top;p++)
        for(register int i=0;i<26;i++)
            if(p->next[i])p->next[i]->dag++;
}
queue<Trie*> que;
void BFS(){
    sam.root->cnt=1;
    que.push(sam.root);
    while(!que.empty()){
        Trie* e=que.front();
        que.pop();
        if(e->next[0]){
            e->next[0]->dag--,e->next[0]->cnta+=e->cnt,e->next[0]->cnt+=e->cnt;
            if(!e->next[0]->dag)
                que.push(e->next[0]);
        }
        for(register int i=1;i<26;i++){
            Trie* eu=e->next[i];
            if(eu){
                eu->dag--;
                if(!eu->dag)que.push(eu);
                eu->cnt += e->cnt;
                eu->cnta += e->cnta;
            }
        }
    }
}
int main(){
    freopen("substring.in","r",stdin);
    freopen("substring.out","w",stdout);
    scanf("%s",s);
    n=strlen(s),sam=SAM(n);
    for(register int i=0;i<n;i++)sam.extends(s[i]);
    DAG(),BFS();
    for(Trie* p=sam.pool;p!=sam.top;p++)res+=p->cnta;
    printf("%I64d",res);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值