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;
}

posted @ 2024-01-17 09:17  Creeper_l  阅读(8)  评论(0编辑  收藏  举报