一些字符串有关的题目

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

因为最近都没有做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);
}

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

对于一个前缀P,我们可以把它分割为P=SSS....SST,其中T是S的一个前缀,并且S尽量短。这个玩意儿可以用kmp来做。

注意到kmp中的next[j]应该是所有满足B[1..next[j]]=B[j-next[j]+1..j]的最大值。那么可以发现只要令|S|=j-next[j]就符合条件。

为了和谐我们应该要让A+B尽量长,那么A+B就要包含(S的个数/K)个S,A就要是(S的个数%K)个S+T。我们只要检查一下是否满足|A+B|>=|A|就行了。

image

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <algorithm>
using namespace std;
#define SZ 2333333
int k;
struct HashKMP
{
char s[SZ+1]; int n;
int next[SZ+3];
void gnext()
{
    n=strlen(s);
    next[0]=-1;
    int j=-1;
    for(int i=1;s[i];i++)
    {
        while(j!=-1&&s[i]!=s[j+1]) j=next[j];
        if(s[i]==s[j+1]) ++j;
        next[i]=j;
    }
    for(int i=0;s[i];i++)
    {
        int len_s=i-next[i],len_t=(i+1)%len_s;
        int cnt_s=(i+1-len_t)/len_s;
        if(cnt_s/k*len_s>=cnt_s%k*len_s+len_t) putchar('1');
        else putchar('0');
    }
}
}ha;
int main()
{
    scanf("%*d%d%s",&k,ha.s);
    ha.gnext();
}

CF557E Ann and Half-Palindrome(Div2 E)

我们把一个串t称为半循环串,当且仅当对于每一个奇数的i(image)均满足image

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

1<=|s|<=5000。

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

注意到(因为是cf)这题是允许一个平方做法的。

我们可以dp出任意一个区间是否是半循环串。用good[i][j]表示i~j是否是半循环串,如果s[i]!=s[j]那么显然good[i][j]=0,否则good[i][j]可以用good[l+2][r-2]来更新。

接下来我们把所有后缀插入trie,并且统计出每一棵子树内半循环串的数量。因为是二叉的,所以可以像splay那样向左向右走来获取答案。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define SZ 5010
#define SZZ 25000100
char s[SZ+1]; int n,k;
bool good[SZ][SZ];
int ch[SZZ][2],val[SZZ],sum[SZZ],M=1;
int alc(int& x)
{
    if(!x) x=++M;
    return x;
}
int main()
{
    scanf("%s%d",s+1,&k);
    n=strlen(s+1);
    for(int l=1;l<=n;l++)
    {
        for(int i=1;i+l-1<=n;i++)
        {
            int j=i+l-1;
            if(s[i]!=s[j]) continue;
            if(i+2<=j-2&&!good[i+2][j-2]) continue;
            good[i][j]=1;
        }
    }
    for(int i=1;i<=n;i++)
    {
        int cur=1;
        for(int j=i;j<=n;j++)
        {
            cur=alc(ch[cur][s[j]-'a']);
            val[cur]+=good[i][j];
        }
    }
    for(int i=M;i>=1;i--) sum[i]=sum[ch[i][0]]+sum[ch[i][1]]+val[i];
    int cur=1; string s;
    while(k>val[cur])
    {
        k-=val[cur];
        if(sum[ch[cur][0]]>=k) cur=ch[cur][0], s+="a";
        else k-=sum[ch[cur][0]], cur=ch[cur][1], s+="b";
    }
    puts(s.c_str());
}

做不下去了...就先做这几题把

posted @ 2016-06-17 22:45  fjzzq2002  阅读(683)  评论(0编辑  收藏  举报