P9016 [USACO23JAN] Find and Replace G 题解
可以把从每个字符开始的操作过程看成是一棵树,对于每一个字符我们都维护一颗树(表示从这个字符开始操作的过程)。因为后面的操作会覆盖掉前面的,所以我们考虑从最后面的操作开始往上建树。首先 \(a-z\) 都指向自己(因为自己如果不变的话值还是自己),对于每一次操作 \((x,s)\)。我们可以依次合并 \(s_{i-1},s_i\)。合并的过程为:建立一个新点(这个点不代表一个字符,可以设为任意字符),左儿子是 \(s_{i-1}\) 这个字符所表示的树,左儿子是 \(s_{i}\) 这个字符所表示的树。一直合并到 \(s_n\) 为止。最后将字符 \(x\) 所代表的树变为这颗合并完的树。
考虑如何查询答案,我们可以用类似于线段树的方式。对于每一个节点记录一个 \(size\) 表示这个子树内有多少个字符。然后通过 \(size\) 和 \(l,r\) 的大小关系判断应该遍历左区间还是右区间即可。
时间复杂度:因为 \(\sum s \le 200000\),所以建树的时间复杂度是 \(O(n)\) 的。又因为这是一颗二叉树,且最后查询的区间大小(叶子节点个数)是 \(r-l+1=O(n)\) 的,所以查询的时间复杂度也是 \(O(n)\) 的,总时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 8e5 + 10;
const int inf = 1000000000000000000;
int l,r,q,root[MAXN],tot;
char a[MAXN],x;
string b[MAXN],s;
struct Node{int ls,rs,size;char c;}tree[MAXN];
inline void solve(int id,int l,int r)
{
if(r <= 0 || l > tree[id].size) return;
if(tree[id].c == '#')
{
solve(tree[id].ls,l,r);
solve(tree[id].rs,l - tree[tree[id].ls].size,r - tree[tree[id].ls].size);
}
else cout << tree[id].c;
}
signed main()
{
cin >> l >> r >> q;
for(int i = 0;i < 26;i++) tree[i] = {i,i,1,char(i + 'a')},root[i] = i;
tot = 25;
for(int i = 1;i <= q;i++) cin >> a[i] >> b[i];
for(int i = q;i >= 1;i--)
{
x = a[i],s = b[i];
int p = root[s[0] - 'a'];
for(int i = 1;i < s.length();i++)
{
int F = s[i] - 'a';
tree[++tot] = {p,root[F],min(inf,tree[p].size + tree[root[F]].size),'#'};
p = tot;
}
root[x - 'a'] = p;
}
solve(root[0],l,r);
return 0;
}
本文作者:Creeper_l
本文链接:https://www.cnblogs.com/Creeperl/p/17969051
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步