一些字符串有关的题目
模板可以在上一篇文章中找到。
因为最近都没有做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|就行了。
#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()均满足。
现在给定一个由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()); }
做不下去了...就先做这几题把