码迷,mamicode.com
首页 > 其他好文 > 详细

一些字符串有关的题目

时间:2016-06-17 23:50:49      阅读:267      评论:0      收藏:0      [点我收藏+]

标签:

模板可以在上一篇文章中找到。

因为最近都没有做codeforces,所以这篇文章的主要题目来源就是codeforces啦~

需要这类题目可以在codeforces上找到hashing、string suffix structures之类的标签。

这些题目都是随便点的,所以有些题目和字符串并没有太大的关系

CF653F Paper Task(非常规比赛)

给一个长度为n的由左右括号做成的字符串,求它子串中不同括号序列的个数。

(注意不是求是合法括号序列的子串数量,而是不同括号序列个数)

1<=n<=500000。

官方题解 http://codeforces.com/blog/entry/43886

这里讲一下官方解法1(话说官方题解实在厉害啊...省略了大量细节)

我们用query(l,r)表示序列的l~r这些有多少个前缀是合法括号序列。

这玩意儿要怎么求呢?我们把左括号当做+1,右括号当做-1,那么我们就是要找一个左端点为l,右端点在[l,r]的区间使得和为0,并且右端点在那之前的区间和都不能为负(为了保证不会出现类似))((的情况)。

那么我们处理出前缀和,然后我们考虑用一个单调队列搞一搞,我们就可以搞出对于每一个qzh[x],后面小于它的第一个qzh。假设小于qzh[l-1]的第一个为qzh[p],那么到了这里我们就要询问[l,min(p-1,r)]有多少个qzh等于qzh[l-1]的元素。

我们可以用sort+二分简单地搞定这个问题,把qzh当做pair<int,int> sort一下,然后再二分一发就可以了。

现在我们在O(nlogn)预处理+O(logn)询问的时间内搞定了query。

接下来我们发现对于每一个后缀s只要建后缀数组求出height,将query(s,n)-query(s,s+height[s]-1)计入答案就行了(减去那玩意儿是为了扣掉上一个后缀算过的答案)。

那么这题就做完了~大概是O(nlogn),不管是不是cf,3s都应该是可以过的。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <vector>
#include <limits>
#include <set>
#include <map>
using namespace std;
#define SZ 666666
int n,k,sa[SZ],t[SZ],rank[SZ],qzh_[SZ],tmpsa[SZ],tmpr[SZ],h[SZ];
char s[SZ];
bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];}
void getsa(int m=500)
{
    s[++n]=0;
    for(int i=0;i<n;i++) rank[i]=s[i], ++qzh_[rank[i]];
    for(int i=1;i<m;i++) qzh_[i]+=qzh_[i-1];
    for(int i=n-1;i>=0;i--) sa[--qzh_[rank[i]]]=i;
    for(int j=1;j<=n;j<<=1)
    {
        int cur=-1;
        for(int i=n-j;i<n;i++) tmpsa[++cur]=i;
        for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j;
        for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]];
        for(int i=0;i<m;i++) qzh_[i]=0;
        for(int i=0;i<n;i++) ++qzh_[tmpr[i]];
        for(int i=1;i<m;i++) qzh_[i]+=qzh_[i-1];
        for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh_[tmpr[i]]]=tmpsa[i];
        m=0;
        for(int i=0;i<n;i++)
            rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m;
        ++m;
    }
    for(int i=0;i<n;i++) rank[sa[i]]=i;
    int p=0;
    for(int i=0;i<n;i++)
    {
        if(p) --p;
        int ls=sa[rank[i]-1];
        while(s[ls+p]==s[i+p]) p++;
        h[rank[i]]=p;
    }
    --n;
    for(int i=1;i<=n;i++) sa[i-1]=sa[i];
    for(int i=0;i<n;i++) rank[sa[i]]=i;
    for(int i=2;i<=n;i++) h[i-1]=h[i];
    h[n]=sa[n]=h[0]=0;
}
int qzh[SZ],dy[2333],gs[SZ],pos[SZ];
int ss[SZ],sn=0;
typedef pair<int,int> pii;
pii qss[SZ];
int query(int l,int r)
{
    if(l>r) return 0;
    return upper_bound(qss,qss+1+n,pii(qzh[l],min(gs[l]-1,r+1)))-qss-1-pos[l];
}
int main()
{
    dy[(]=1; dy[)]=-1;
    scanf("%*d%s",s);
    n=strlen(s); getsa();
    for(int i=1;i<=n;i++) qzh[i]=qzh[i-1]+dy[s[i-1]];
    for(int i=n;i>=0;i--)
    {
        int x=qzh[i];
        while(sn&&qzh[ss[sn]]>=x) --sn;
        if(!sn) gs[i]=n+1;
        else gs[i]=ss[sn];
        ss[++sn]=i;
    }
    for(int i=0;i<=n;i++) qss[i]=pii(qzh[i],i);
    sort(qss,qss+1+n);
    for(int i=0;i<=n;i++) pos[qss[i].second]=i;
    long long ans=0;
    for(int i=0;i<n;i++)
    {
        ans+=query(sa[i],n-1);
        ans-=query(sa[i],sa[i]+h[i]-1);
    }
    printf("%I64d\n",ans);
}

CF631D Messenger(Div2 D)

给定两个压缩过的字符串s和t,求s在t中的出现次数。

压缩方式:3-a 2-b展开为aaabb,类似这样。

如果我们把3-a、2-b这种东西叫做压缩节,那么保证每个字符串的压缩节个数<=200000。保证每个压缩节的前面那个玩意儿(展开次数)<=1000000。

官方题解 http://codeforces.com/blog/entry/43551

首先我们应该把压缩节“化简”一下以便于下面的处理。这里指把1-a 1-a化成2-a这样子。

当s的压缩节<=2时可以特判。

其它情况下我们需要找到一对(l,r)使得s[l+1...r-1]=t[2...|t|-1]并且左边比较靠谱,右边也比较靠谱,我们就可以把t[2...|t|-1]扔去和s进行kmp,对于每一处匹配暴力算一算是否满足。

坑点:虽然看起来输入里的展开次数<=1000000,可是你一合并就爆int了...

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define SZ 666666
int n,m;
typedef pair<long long,char> pic;
pic as[SZ],bs[SZ];
#define f_ first
#define s_ second
void m_1()
{
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        if(as[i].s_==bs[1].s_&&as[i].f_>=bs[1].f_) ans+=as[i].f_-bs[1].f_+1;
    }
    printf("%I64d\n",ans);
}
void m_2()
{
    long long ans=0;
    for(int i=1;i+1<=n;i++)
    {
        if(as[i].s_==bs[1].s_&&as[i].f_>=bs[1].f_&&as[i+1].s_==bs[2].s_&&as[i+1].f_>=bs[2].f_) ++ans;
    }
    printf("%I64d\n",ans);
}
int ms=0,ml[SZ],mr[SZ];
struct HashKMP
{
pic s[SZ+1];
int n,next[SZ+3];
void gnext()
{
    n=0;
    while(s[n].s_) ++n;
    next[0]=-1;
    int j=-1;
    for(int i=1;i<n;i++)
    {
        while(j!=-1&&s[i].s_!=s[j+1].s_) j=next[j];
        if(s[i].s_==s[j+1].s_) ++j;
        next[i]=j;
    }
}
void kmp(pic* a)
{
    int j=-1;
    for(int i=0;a[i].s_;i++)
    {
        while(j!=-1&&s[j+1]!=a[i]) j=next[j];
        if(s[j+1]==a[i]) ++j;
        if(j==n-1) ++ms, ml[ms]=i-n+1, mr[ms]=i+2;
    }
}
}ha;
pic hb[SZ];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int a; char b[3];
        scanf("%d-%s",&a,b);
        if(i>1&&as[i-1].s_==b[0])
        {
            as[i-1].f_+=a; --i; --n; continue;
        }
        as[i]=pic(a,b[0]);
    }
    for(int i=1;i<=m;i++)
    {
        int a; char b[3];
        scanf("%d-%s",&a,b);
        if(i>1&&bs[i-1].s_==b[0])
        {
            bs[i-1].f_+=a; --i; --m; continue;
        }
        bs[i]=pic(a,b[0]);
    }
    if(m==1) {m_1(); return 0;}
    if(m==2) {m_2(); return 0;}
    for(int i=2;i<=m-1;i++) ha.s[i-2]=bs[i];
    ha.gnext();
    for(int i=1;i<=n;i++) hb[i-1]=as[i];
    ha.kmp(hb);
    long long ans=0;
    for(int i=1;i<=ms;i++)
    {
        int a=ml[i],b=mr[i];
        if(a<1||b<1||a>n||b>n) continue;
        if(as[a].s_==bs[1].s_&&as[a].f_>=bs[1].f_);else continue;
        if(as[b].s_==bs[m].s_&&as[b].f_>=bs[m].f_);else continue;
        ++ans;
    }
    printf("%I64d\n",ans);
}

CF633C Spy Syndrome 2(非常规比赛)

有一种对于句子的加密方法是这样的:先将所有字母转成小写,然后把所有单词反序之后拼接在一起。

给定一个加密后的单词和一个包含了一些单词的字典,求任意一个还原方法。

官方题解 http://codeforces.com/blog/entry/43392

 

CF526D Om Nom and Necklace(非常规比赛)

我们叫一个串S为常规串当且仅当存在两个串A、B(均可为空)使得S=A+B+A+B+...+A。

其中加号表示字符串连接,需要注意的是串需要以A开头与结尾,并且需要有k+1个A与k个B,k为一个给定数。

找到一个串S的哪些前缀是常规串,按01输出。

1<=n,k<=1000000。

官方题解 http://codeforces.com/blog/entry/17281

 

CF557E Ann and Half-Palindrome(Div2 E)

我们把一个串t称为半循环串,当且仅当对于每一个技术分享均满足技术分享

现在给定一个串s,求将s的所有子串中半循环串按字典序从小到大排序之后第k大的半循环串。

1<=|s|<=5000。

官方题解 http://codeforces.com/blog/entry/18943

 

CF547E Mike and Friends(感人至深的Div1 E)

有n个人每人有一个字符串作为他们的手机号码。

有一天每一个人要给所有他的号码子串打电话(可能会多次打同一个电话,也会打给自己)。

我们定义call(a,b)为a打给b电话的次数。

有q次询问,每次询问给定l、r、k,询问在这天技术分享

1<=n<=200000,1<=q<=500000,字符串总长<=200000。

官方题解 http://codeforces.com/blog/entry/18126

 

施工中...

一些字符串有关的题目

标签:

原文地址:http://www.cnblogs.com/zzqsblog/p/5595326.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!