[USACO23JAN] Find and Replace G题解

洛谷[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}) 1lrmin(S,1018)
  • r − l + 1 ≤ 2 × 1 0 5 r-l+1\leq 2\times 10^5 rl+12×105
  • ∑ ∣ s ∣ ≤ 2 × 1 0 5 \sum|s|\leq 2\times 10^5 s2×105

题解

首先,在最后一次操作之后加 26 26 26次操作,分别是 a → a , b → b , … , z → z a\to a,b\to b,\dots,z\to z aa,bb,,zz

把每次操作看成一个点,对于操作中的字符串的每一个字符 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=l1,对于每一个节点,在遍历其儿子的时候,如果 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 rl+1个叶子节点。先假设所有字符串的长度都大于等于 2 2 2,则因为 r − l + 1 ≤ 2 × 1 0 5 r-l+1\leq 2\times 10^5 rl+12×105,且每次向下遍历都会使节点数量增加至少一倍,所以这部分的时间复杂度为 O ( l e n ) O(len) O(len),其中 l e n = r − l + 1 len=r-l+1 len=rl+1

遍历完 r − l + 1 r-l+1 rl+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} 1lr1018,所以对于大于 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;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值