字符串专题 1
你说的对,但是只要不考试干啥都行。
听说这字符串专题后边还有打击复读,恐怖!后边多项式和生成函数还有百鸽笼,这算多项式吗!而且机房好冷!
省选难度标了,Day1 T2 和 Day2 T2 都是紫挺震撼。
CF547E Mike and Friends
犹记当年 tlecoders 写了个 SA + 主席树怒 T 90pts。然而 CF 上跑的飞快。
这题有不少做法,可以建 AC 自动机离线询问,变成 Trie 上一条链加(暴力)和 fail 树子树查询(树状数组),也可以 SA 判断能匹配的左右端点然后主席树维护,也可以 SAM 线段树合并。
保留 tlecoders 的代码缩进吧。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int n, num, m;
char t[400010];
int s[400010], sa[400010], rk[400010], rk2[400010], key[400010], cnt[400010], bel[400010];
int len[400010], ht[400010], pos[400010], id[400010];
void getsa() {
int m = 127;
for (int i = 1; i <= n; i++) {
rk[i] = s[i];
cnt[rk[i]]++;
}
for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--) sa[cnt[rk[i]]--] = i;
for (int w = 1;; w <<= 1) {
int p = 0;
for (int i = n; i > n - w; i--) id[++p] = i;
for (int i = 1; i <= n; i++) {
if (sa[i] > w)
id[++p] = sa[i] - w;
}
memset(cnt + 1, 0, m * 4);
for (int i = 1; i <= n; i++) {
key[i] = rk[id[i]];
cnt[key[i]]++;
}
for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--) sa[cnt[key[i]]--] = id[i];
memcpy(rk2 + 1, rk + 1, n * 4);
p = 0;
for (int i = 1; i <= n; i++) {
rk[sa[i]] = (rk2[sa[i]] == rk2[sa[i - 1]] && rk2[sa[i] + w] == rk2[sa[i - 1] + w]) ? p : ++p;
}
if (p == n) {
for (int i = 1; i <= n; i++) sa[rk[i]] = i;
break;
}
m = p;
}
}
void getht() {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 0)
continue;
if (k)
k--;
while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
ht[rk[i]] = k;
}
}
struct ST {
int st[400010][21];
void build() {
for (int i = 1; i <= n; i++) st[i][0] = ht[i];
for (int j = 1; j <= __lg(n); j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
int lcp(int l, int r) {
if (l == r)
return n - l + 1;
l++;
int k = __lg(r - l + 1);
return min(st[l][k], st[r - (1 << k) + 1][k]);
}
} st;
int getl(int id) {
int l = 1, r = pos[id];
while (l < r) {
int mid = (l + r) >> 1;
if (st.lcp(mid, pos[id]) < len[id])
l = mid + 1;
else
r = mid;
}
return l;
}
int getr(int id) {
int l = pos[id], r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (st.lcp(pos[id], mid) < len[id])
r = mid - 1;
else
l = mid;
}
return l;
}
namespace Seg {
#define lson tree[rt].ls
#define rson tree[rt].rs
int t, rt[400010];
struct node {
int ls, rs, sum;
} tree[400010 << 5];
void pushup(int rt) { tree[rt].sum = tree[lson].sum + tree[rson].sum; }
void build(int &rt, int l, int r) {
rt = ++t;
if (l == r)
return;
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
}
void update(int &rt, int pre, int L, int R, int pos) {
tree[rt = ++t] = tree[pre];
if (L == R) {
tree[rt].sum++;
return;
}
int mid = (L + R) >> 1;
if (pos <= mid)
update(lson, tree[pre].ls, L, mid, pos);
else
update(rson, tree[pre].rs, mid + 1, R, pos);
pushup(rt);
}
int query(int x, int y, int L, int R, int l, int r) {
if (l <= L && R <= r)
return tree[y].sum - tree[x].sum;
int mid = (L + R) >> 1, val = 0;
if (l <= mid)
val += query(tree[x].ls, tree[y].ls, L, mid, l, r);
if (mid < r)
val += query(tree[x].rs, tree[y].rs, mid + 1, R, l, r);
return val;
}
}
int main() {
scanf("%d%d", &num, &m);
for (int i = 1; i <= num; i++) {
scanf("%s", t + 1);
len[i] = strlen(t + 1);
pos[i] = n + 1;
for (int j = 1; j <= len[i]; j++) {
s[++n] = t[j] - 'a' + 1;
bel[n] = i;
}
n++;
}
getsa();
getht();
st.build();
Seg::build(Seg::rt[0], 0, num);
for (int i = 1; i <= n; i++) Seg::update(Seg::rt[i], Seg::rt[i - 1], 0, num, bel[sa[i]]);
for (int i = 1; i <= num; i++) pos[i] = rk[pos[i]];
while (m--) {
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
int L = getl(k), R = getr(k);
printf("%d\n", Seg::query(Seg::rt[L - 1], Seg::rt[R], 0, num, l, r));
}
return 0;
}
CF504E Misha and LCP on Tree
前边几个都挺板板的,这题 *3000 大概率因为卡常。
直接二分哈希,单次要求找 \(k\) 级祖先,可以写长剖 \(O(1)\),我学了个 \(O(\log\log n)\) 的重剖写法,就是预处理跳 \(2^k\) 条重链的结果。然后总复杂度就是 \(O(n\log n\log\log n)\) 了。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int mod=1000000007,prm=131,invprm=190839696;
int n,m,p[300010],invp[300010];
char s[300010];
struct node{
int v,next;
}edge[600010];
int t,head[300010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,dfn[300010],dep[300010],fa[300010],rk[300010],top[300010][5],sz[300010],son[300010];
int up[300010],down[300010];
void dfs1(int x,int f){
sz[x]=1;dep[x]=dep[f]+1;fa[x]=f;
up[x]=(1ll*s[x]*p[dep[f]]+up[f])%mod;
down[x]=(1ll*down[f]*prm+s[x])%mod;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs1(edge[i].v,x);
sz[x]+=sz[edge[i].v];
if(sz[son[x]]<sz[edge[i].v])son[x]=edge[i].v;
}
}
}
void dfs2(int x,int f,int tp){
dfn[x]=++num;rk[num]=x;top[x][0]=tp;
for(int i=1;i<=4;i++)top[x][i]=top[fa[top[x][i-1]]][i-1];
if(son[x])dfs2(son[x],x,tp);
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f&&edge[i].v!=son[x])dfs2(edge[i].v,x,edge[i].v);
}
}
int query(int x,int k){
for(int i=4;i>=0;i--)if(top[x][i]&&k>dep[x]-dep[top[x][i]])k-=dep[x]-dep[top[x][i]]+1,x=fa[top[x][i]];
return !x?0:rk[dfn[x]-k];
}
int lca(int x,int y){
while(top[x][0]!=top[y][0]){
if(dep[top[x][0]]<dep[top[y][0]])swap(x,y);
x=fa[top[x][0]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
int getup(int x,int y){
y=fa[y];
return 1ll*(up[x]-up[y]+mod)%mod*invp[dep[y]]%mod;
}
int getdown(int x,int y){
return (down[y]-1ll*down[x]*p[dep[y]-dep[x]]%mod+mod)%mod;
}
int get(int u,int v,int lc,int len){
int d=dep[u]-dep[lc]+1;
if(len<=d){
v=query(u,len-1);
return getup(u,v);
}
int hs1=getup(u,lc);len-=d;
d=dep[v]-dep[lc]-len;
v=query(v,d);
int hs2=getdown(lc,v);
return (1ll*hs1*p[len]+hs2)%mod;
}
bool check(int mid,int a,int b,int c,int d,int lcab,int lccd){
return get(a,b,lcab,mid)==get(c,d,lccd,mid);
}
int main(){
scanf("%d%s",&n,s+1);p[0]=invp[0]=1;
for(int i=1;i<=n;i++)p[i]=1ll*p[i-1]*prm%mod,invp[i]=1ll*invp[i-1]*invprm%mod;
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,0,1);
scanf("%d",&m);
while(m--){
int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
int lcab=lca(a,b),lccd=lca(c,d);
int l=0,r=min(dep[a]+dep[b]-2*dep[lcab]+1,dep[c]+dep[d]-2*dep[lccd]+1);
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid,a,b,c,d,lcab,lccd))l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
return 0;
}
CF204E Little Elephant and Strings
广义 SAM,然后对于每个字符在 SAM 上的位置类似虚树一样按 dfs 序排序,每个点权值加,相邻 lca 权值减,最后在每个位置倍增跳一下。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n,k;
string s[100010];
long long ans;
int pos[200010];
struct SAM{
int cnt,fa[200010],len[200010],trie[200010][26];
SAM(){cnt=1;}
int ins(int ch,int last){
if(trie[last][ch]){
int p=last,q=trie[p][ch];
if(len[p]+1==len[q])return q;
else{
len[++cnt]=len[p]+1;
for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
fa[cnt]=fa[q];fa[q]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
return cnt;
}
}
int p=last;last=++cnt;
len[last]=len[p]+1;
while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
if(!p){
fa[last]=1;return last;
}
int q=trie[p][ch];
if(len[p]+1==len[q]){
fa[last]=q;return last;
}
len[++cnt]=len[p]+1;
for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
return last;
}
void calc(string s){
int p=1;
for(int i=0;i<s.length();i++){
p=trie[p][s[i]-'a'];
pos[i]=p;
}
}
}sam;
struct node{
int v,next;
}edge[200010];
int t,head[200010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,sz[200010],fa[200010][20],dep[200010],dfn[200010];
void dfs1(int x,int f){
fa[x][0]=f;dep[x]=dep[f]+1;dfn[x]=++num;
for(int i=1;i<=__lg(sam.cnt);i++)fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f)dfs1(edge[i].v,x);
}
}
bool cmp(int a,int b){
return dfn[a]<dfn[b];
}
int lca(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=__lg(sam.cnt);i>=0;i--)if(dep[fa[y][i]]>=dep[x])y=fa[y][i];
if(x==y)return x;
for(int i=__lg(sam.cnt);i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
void dfs2(int x,int f){
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs2(edge[i].v,x);
sz[x]+=sz[edge[i].v];
}
}
}
int getans(int x){
if(sz[x]>=k)return sam.len[x];
for(int i=__lg(sam.cnt);i>=0;i--)if(fa[x][i]&&sz[fa[x][i]]<k)x=fa[x][i];
return sam.len[fa[x][0]];
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>s[i];
int last=1;
for(int j=0;j<s[i].length();j++)last=sam.ins(s[i][j]-'a',last);
}
for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
dfs1(1,0);
for(int i=1;i<=n;i++){
sam.calc(s[i]);
sort(pos,pos+s[i].length(),cmp);
for(int j=0;j<s[i].length();j++)sz[pos[j]]++;
for(int j=1;j<s[i].length();j++)sz[lca(pos[j],pos[j-1])]--;
}
dfs2(1,0);
for(int i=1;i<=n;i++){
sam.calc(s[i]);ans=0;
for(int j=0;j<s[i].length();j++)ans+=getans(pos[j]);
cout<<ans<<' ';
}
cout<<'\n';
return 0;
}
CF1012D AB-Strings
首先连续若干个可以缩一个,然后睡大觉。胡了一下差不多是每次两边一起消掉一段相同字符的样子,然后看题解发现还要巨大分讨避免长度不平均。tourist 题解也挺语焉不详的。
写 nm,专题 2 都开了。
gym103427M String Problem
这一场怎么不是原神就是 A-SOUL。
首先显然答案是后缀。发现这是个类 Lyndon 分解状物,于是考虑什么样的后缀可以作为答案。答案一定是上一次答案的后缀,证明见 Lyndon 分解。假设一个后缀添上一个字符可能成为答案,那么它一定是上一次答案的一个 border,证明考虑非 border 字典序一定比开头小。那么暴力维护复杂度就是线性的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
char s[1000010],a[1000010];
int nxt[1000010];
int main(){
scanf("%s",s+1);n=strlen(s+1);
puts("1 1");
a[1]=s[1];
for(int i=2,j,len=1;i<=n;i++){
while(len&&a[nxt[len]+1]<s[i])len=nxt[len];
a[++len]=s[i];
printf("%d %d\n",i-len+1,i);
j=nxt[len-1];
while(j&&a[len]!=a[j+1])j=nxt[j];
if(len!=1&&a[len]==a[j+1])j++;
nxt[len]=j;
}
}
gym103409J
看不懂题面,感恩!原来我没有大脑,那没事了!
首先二分出这个 \(k\) 在哪个长度范围内。然后看字典序限制怎么做。
如果没有长度限制怎么做?我不会。我菜!建出后缀树,按 dfs 序扫就是子串的字典序。处理字典序可以记录每个节点最大 endpos 然后减长度得到首字母。
然后加上限制,发现每个节点能贡献的是一段长度区间,那线段树扫描线一下就没了。
然而卡空间,挺没木琴的。SAM 要用 map 存。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
int n,m;
char s[1000010];
struct SAM{
int cnt,last,fa[2000010],len[2000010],pos[2000010];
map<int,int>trie[2000010];
SAM(){cnt=last=1;}
void ins(int ch,int id){
int p=last;last=++cnt;pos[last]=id;
len[last]=len[p]+1;
while(p&&trie[p].find(ch)==trie[p].end())trie[p][ch]=cnt,p=fa[p];
if(!p){
fa[last]=1;return;
}
int q=trie[p][ch];
if(len[p]+1==len[q]){
fa[last]=q;return;
}
len[++cnt]=len[p]+1;
trie[cnt]=trie[q];
fa[cnt]=fa[q];pos[cnt]=pos[q];fa[q]=cnt;fa[last]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
}sam;
struct gra{
int v,next;
}edge[2000010];
int tot,head[2000010];
void add(int u,int v){
edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
void dfs1(int x){
for(int i=head[x];i;i=edge[i].next){
dfs1(edge[i].v);
sam.pos[x]=max(sam.pos[x],sam.pos[edge[i].v]);
}
}
int num,dfn[2000010],rk[2000010];
vector<int>g[1000010];
void dfs2(int x){
dfn[x]=++num;rk[num]=x;
vector<pair<char,int> >tmp;
for(int i=head[x];i;i=edge[i].next){
tmp.push_back(make_pair(s[sam.pos[edge[i].v]-sam.len[x]],edge[i].v));
}
sort(tmp.begin(),tmp.end());
for(pair<char,int> p:tmp){
int v=p.second;
g[sam.len[x]+1].push_back(v);
g[sam.len[v]+1].push_back(-v);
dfs2(v);
}
}
struct Ques{
long long k;
int id;
bool operator<(const Ques&s)const{
return k<s.k;
}
}q[1000010];
pair<int,int>ans[1000010];
#define lson rt<<1
#define rson rt<<1|1
int tree[8000010];
void pushup(int rt){
tree[rt]=tree[lson]+tree[rson];
}
void update(int rt,int L,int R,int pos,int val){
if(L==R){
tree[rt]+=val;return;
}
int mid=(L+R)>>1;
if(pos<=mid)update(lson,L,mid,pos,val);
else update(rson,mid+1,R,pos,val);
pushup(rt);
}
int query(int rt,int L,int R,int k){
if(L==R)return rk[L];
int mid=(L+R)>>1;
if(k<=tree[lson])return query(lson,L,mid,k);
else return query(rson,mid+1,R,k-tree[lson]);
}
int main(){
scanf("%s",s+1);n=strlen(s+1);
reverse(s+1,s+n+1);
for(int i=1;i<=n;i++)sam.ins(s[i]-'a',i);
for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
dfs1(1);dfs2(1);
scanf("%d",&m);
for(int i=1;i<=m;i++)scanf("%lld",&q[i].k),q[i].id=i,ans[i]=make_pair(-1,-1);
sort(q+1,q+m+1);
long long sum=0;
int p=1;
for(int i=1;i<=n;i++){
for(int x:g[i]){
if(x>0)update(1,1,sam.cnt,dfn[x],1);
else update(1,1,sam.cnt,dfn[-x],-1);
}
long long ret=sum;sum+=tree[1];
while(p<=m&&q[p].k>ret&&q[p].k<=sum){
int pos=query(1,1,sam.cnt,q[p].k-ret);
ans[q[p].id]=make_pair(n-sam.pos[pos]+1,n-sam.pos[pos]+i);
p++;
}
}
for(int i=1;i<=m;i++)printf("%d %d\n",ans[i].first,ans[i].second);
return 0;
}
CF914F Substrings in a String
典中典之 bitset 字符串匹配。详见 https://www.cnblogs.com/gtm1514/p/16909525.html。
CF700E Cool Slogans
看见串串没思路先建个 SAM 出来。然后考虑这个序列一定是 parent 树上从上往下一个序列(可以间断),发现“出现两次”这个限制可以在 parent 树上比较轻松地维护。线段树合并维护 endpos,然后设当前节点 \(x\) 从节点 \(pre\) 转移过来,那么在 \(pre\) 的线段树上查,对于 \(x\) 的一个位置 \(pos\) 若在 \([pos-len_x+len_{pre},pos-1]\) 里出现了 \(pre\) 则可以转移。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int n;
char s[200010];
struct SAM{
int cnt,last,fa[400010],len[400010],trie[400010][26],pos[400010];
SAM(){cnt=last=1;}
void ins(int ch,int id){
int p=last;last=++cnt;pos[last]=id;
len[last]=len[p]+1;
while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
if(!p){
fa[last]=1;return;
}
int q=trie[p][ch];
if(len[p]+1==len[q]){
fa[last]=q;return;
}
len[++cnt]=len[p]+1;
for(int i=0;i<26;i++)trie[cnt][i]=trie[q][i];
fa[cnt]=fa[q];pos[cnt]=pos[q];fa[q]=cnt;fa[last]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
}sam;
struct gra{
int v,next;
}edge[400010];
int tot,head[400010];
void add(int u,int v){
edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
struct node{
#define lson tree[rt].ls
#define rson tree[rt].rs
int ls,rs,sum;
}tree[400010<<6];
int t,rt[400010];
void pushup(int rt){
tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void update(int &rt,int L,int R,int pos){
if(!rt)rt=++t;
if(L==R){
tree[rt].sum++;return;
}
int mid=(L+R)>>1;
if(pos<=mid)update(lson,L,mid,pos);
else update(rson,mid+1,R,pos);
pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
if(!rt)return 0;
if(l<=L&&R<=r)return tree[rt].sum;
int mid=(L+R)>>1,val=0;
if(l<=mid)val+=query(lson,L,mid,l,r);
if(mid<r)val+=query(rson,mid+1,R,l,r);
return val;
}
int merge(int x,int y,int l,int r){
if(!x||!y)return x|y;
int rt=++t;
if(l==r){
tree[rt].sum=tree[x].sum+tree[y].sum;
return rt;
}
int mid=(l+r)>>1;
tree[rt].ls=merge(tree[x].ls,tree[y].ls,l,mid);
tree[rt].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
pushup(rt);
return rt;
}
void dfs1(int x){
for(int i=head[x];i;i=edge[i].next){
dfs1(edge[i].v);
rt[x]=merge(rt[x],rt[edge[i].v],1,n);
}
}
void getpre(){
for(int i=2;i<=sam.cnt;i++)add(sam.fa[i],i);
int p=1;
for(int i=1;i<=n;i++){
p=sam.trie[p][s[i]-'a'];
update(rt[p],1,n,i);
}
dfs1(1);
}
int ans,dp[400010],pre[400010];
void dfs(int x,int f){
if(!f);
else if(f==1){
dp[x]=1;pre[x]=x;
}
else{
if(query(rt[pre[f]],1,n,sam.pos[x]-sam.len[x]+sam.len[pre[f]],sam.pos[x]-1))dp[x]=dp[f]+1,pre[x]=x;
else dp[x]=dp[f],pre[x]=pre[f];
}
ans=max(ans,dp[x]);
for(int i=head[x];i;i=edge[i].next)dfs(edge[i].v,x);
}
int main(){
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)sam.ins(s[i]-'a',i);
getpre();
dfs(1,0);
printf("%d\n",ans);
return 0;
}