洛谷[USACO23JAN] Find and Replace G
题目大意
你有一个字符串 S S S,最开始 S S S中只有一个字符 a a a。然后你要对字符串 S S S进行若干次操作,每次操作将 S S S中的每一个字符 c c c替换成某个字符串 s s s(比如对于字符串 b a l l ball ball,将其中的 l l l替换为 n a na na后将变为 b a n a n a banana banana)。现在给出 l , r l,r l,r,你需要输出 S l . . . r S_{l...r} Sl...r(即 S S S的第 l l l个字符到第 r r r个字符对应的子串)。
- 1 ≤ l ≤ r ≤ min ( ∣ S ∣ , 1 0 18 ) 1\leq l\leq r\leq \min(|S|,10^{18}) 1≤l≤r≤min(∣S∣,1018)
- r − l + 1 ≤ 2 × 1 0 5 r-l+1\leq 2\times 10^5 r−l+1≤2×105
- ∑ ∣ s ∣ ≤ 2 × 1 0 5 \sum|s|\leq 2\times 10^5 ∑∣s∣≤2×105
题解
首先,在最后一次操作之后加 26 26 26次操作,分别是 a → a , b → b , … , z → z a\to a,b\to b,\dots,z\to z a→a,b→b,…,z→z。
把每次操作看成一个点,对于操作中的字符串的每一个字符 c c c,找到在这次操作之后的第一个修改字符为 c c c的操作,从原来的操作的点向这个修改字符为 c c c的操作的点连一条有向边。这样,整个图就变成了一个有向无环图。而除了最后加的 26 26 26次操作的点没有出度,其他的点都有出度。我们称这 26 26 26个点为叶子节点。
用拓扑排序,求一遍每个点能到达的叶子节点的个数 s i z siz siz, s i z siz siz实际就是每个点表示的操作的字符串到最后能变成的字符串的长度。
然后,将这个图遍历一遍。令 k = l − 1 k=l-1 k=l−1,对于每一个节点,在遍历其儿子的时候,如果 k k k大于等于这个儿子的 s i z siz siz,则 k − = s i z k-=siz k−=siz;否则遍历这个儿子。当 k = 0 k=0 k=0时,我们将遍历到一个叶子节点,这个叶子节点的字符就是位置 l l l的字符。这样我们就可以 O ( m ) O(m) O(m)找到位置 l l l的字符。
继续遍历,每到达一个叶子节点,我们就输出一次这个叶子节点的字符(前面位置 l l l的字符也要输出),总共需要遍历 r − l + 1 r-l+1 r−l+1个叶子节点。先假设所有字符串的长度都大于等于 2 2 2,则因为 r − l + 1 ≤ 2 × 1 0 5 r-l+1\leq 2\times 10^5 r−l+1≤2×105,且每次向下遍历都会使节点数量增加至少一倍,所以这部分的时间复杂度为 O ( l e n ) O(len) O(len),其中 l e n = r − l + 1 len=r-l+1 len=r−l+1。
遍历完 r − l + 1 r-l+1 r−l+1个叶子节点之后,答案也输出完了,直接退出即可。
上面假设了所有字符串的长度都大于等于 2 2 2,但事实上一些字符串的长度可能等于 1 1 1,这会导致可能出现一次操作将一个字符变为许多个相同的字符,然后下面对这个字符进行了一长串的操作,这样就不能保证时间复杂度了。
那我们该怎么办呢?我们可以对字符串长度为 1 1 1的操作的点在连边的时候再放入一个并查集,那么在查询的时候直接 f i n d ( u ) find(u) find(u)即可得到这个字符最终能得到的长度为 1 1 1的字符串,这样就能保证时间复杂度了。
总时间复杂度为 O ( l e n ) O(len) O(len)。
注
- 因为后面加了 26 26 26次操作,所以一些数组要开大一些
- s i z siz siz有可能大于 1 0 18 10^{18} 1018,但因为 1 ≤ l ≤ r ≤ 1 0 18 1\leq l\leq r\leq 10^{18} 1≤l≤r≤1018,所以对于大于 1 0 18 10^{18} 1018的 s i z siz siz,让其等于 1 e 18 + 1 1e18+1 1e18+1即可
code
#include<bits/stdc++.h>
using namespace std;
int m,now=0,s1=0,t1,lt[35],ct[200105],fa[200105];
long long l,r,k,len,inf=1e18;
char s[200105],t[200005];
vector<int>v[200105];
queue<int>q;
struct node{
char ch;
int bg,to[35];
long long siz,hv[35];
}w[200105];
char in(){
char ch=getchar();
while(ch<'a'||ch>'z') ch=getchar();
return ch;
}
void pt(){
for(int i=m+1;i<=m+26;i++) q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<v[u].size();i++){
int p=w[u].ch-'a'+1;
if(w[u].siz>inf/w[v[u][i]].hv[p]) w[v[u][i]].siz=inf+1;
else w[v[u][i]].siz+=w[u].siz*w[v[u][i]].hv[p];
if(w[v[u][i]].siz>inf) w[v[u][i]].siz=inf+1;
--ct[v[u][i]];
if(!ct[v[u][i]]) q.push(v[u][i]);
}
}
}
int find(int ff){
if(fa[ff]!=ff) fa[ff]=find(fa[ff]);
return fa[ff];
}
void dfs(int u){
if(u>m){
printf("%c",u-m+'a'-1);
++now;return;
}
if(!k&&w[u].bg+1==w[u+1].bg){
dfs(find(u));return;
}
for(int i=w[u].bg;i<w[u+1].bg;i++){
int p=s[i]-'a'+1;
if(k>=w[w[u].to[p]].siz) k-=w[w[u].to[p]].siz;
else{
dfs(w[u].to[p]);
if(now==len) return;
}
}
}
int main()
{
scanf("%lld%lld%d",&l,&r,&m);
k=l-1;len=r-l+1;
for(int i=1;i<=m;i++){
w[i].ch=in();
scanf("%s",t+1);
t1=strlen(t+1);
w[i].bg=s1+1;
for(int o=1;o<=t1;o++) s[++s1]=t[o];
}
for(int i=1;i<=26;i++){
w[m+i].ch=i+'a'-1;
w[m+i].siz=1;
w[m+i].bg=s1+1;
s[++s1]=i+'a'-1;
lt[i]=m+i;
}
for(int i=1;i<=m+26;i++) fa[i]=i;
w[m+27].bg=s1+1;
for(int i=m;i>=1;i--){
for(int j=w[i].bg;j<w[i+1].bg;j++){
int p=s[j]-'a'+1;
++w[i].hv[p];
if(w[i].to[p]) continue;
w[i].to[p]=lt[p];
++ct[i];
v[lt[p]].push_back(i);
}
if(w[i].bg+1==w[i+1].bg) fa[i]=lt[s[w[i].bg]-'a'+1];
lt[w[i].ch-'a'+1]=i;
}
pt();
dfs(lt[1]);
return 0;
}