[HNOI2019] JOJO

2022/6/12 23:22:13

本文主要是介绍[HNOI2019] JOJO,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、题目

点此看题

二、解法

首先不考虑 \(2\) 操作,考虑怎么向串的末尾加入 \(x\) 个字符 \(c\),下文将其称之为“一段”。

注意到关键条件:对于加入的字符 \(c\),保证之前串尾的字符不是 \(c\),考虑整段整段地跑 \(\tt kmp\),在两个段完全相同(指个数和字符)时跳出。根据题目条件,如果两个段部分匹配,后面再加字符的话就会匹配不上,所以段不完全相同的时候还要暴力跳 \(nxt\) 指针。

考虑在跳跃的时候计算新增 \(\tt border\) 的总和,假设末尾的段是 \((x,c)\),匹配到前面的段是 \((y,c)\):

  • \(x=y\),对应的 \(\tt border\) 是公差为 \(1\) 的等差序列,此时找到匹配可以直接跳出。
  • \(x<y\),会直接失配,但是对应的 \(\tt border\) 还是公差为 \(1\) 的等差序列。
  • \(x>y\),可以部分地确定 \(y\) 个末尾段的 \(\tt border\),仍然构成等差序列,确定剩下的部分我们需要跳 \(nxt\) 指针。

这个过程需要记录最后一段确定到的位置 \(cur\),但是注意跳到第一段时,匹配条件放宽成 \(x\geq y\),未确定的 \(\tt border\) 长度都应该是 \(y\),需要特判。时间复杂度和传统的 \(\tt kmp\) 是一致的,都是 \(O(n)\)

现在考虑怎么可持久化,把操作离线下来,发现会构成一棵操作树,每个时刻的串对应树上的一个状态,树上的边对应新添加的段。那么在操作树上处理询问,就只需要支持可撤销 \(\tt kmp\) 即可。

由于 \(\tt kmp\) 不会改变前面的信息,我们想限制单次插入在 \(O(\log n)\) 内的时间完成。用到的关键结论是:若最长 \(\tt border\) 长度 \(L>n/2\),所有长度大于 \(n/2\) 的 \(\tt border\) 构成一个公差为 \(n-L\) 的等差数列

并且这些等差数列对应的匹配点都是一致的,所以在跳 \(nxt\) 指针至少一次之后,可以直接跳过一个等差数列。那么长度每次至少会减少一半,所以单次插入的时间复杂度 \(O(\log n)\)

时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,id[M],f[M],ans[M],len[M];vector<int> g[M];
struct node{int x,c;}s[M],a[M];
int C(int x) {return x*(x-1)/2;}
void dfs(int u,int n)
{
	if(n==-1)
	{
		for(int v:g[u]) dfs(v,n+1);
		return ;
	}
	s[n]=a[u];len[n+1]=len[n]+a[u].x;
	if(!n) ans[u]=C(s[n].x);
	else
	{
		int cur=0,j=f[n];
		for(int tj=0;~j;j=tj)
		{
			if(j<f[n] && f[j]>j/2)
				tj=(j-1)%(j-f[j])+1;
			else tj=f[j];
			if(s[j].c==s[n].c)
			{
				int i=min(s[j].x,s[n].x);
				if(cur<i) ans[u]+=C(len[j]+i+1)
					-C(len[j]+cur+1),cur=i;
			}
			if(s[j].x==s[n].x && s[j].c==s[n].c)
				break;
		}
		if(j==-1 && s[0].c==s[n].c && s[0].x<s[n].x)
			ans[u]+=s[0].x*(s[n].x-cur),j=0;
		f[n+1]=j+1;
	}
	for(int v:g[u])
		ans[v]=ans[u],dfs(v,n+1);
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int op=read(),x=read();char c;
		if(op==1)
		{
			cin>>c;
			a[++m]={x,c-'a'};id[i]=m;
			g[id[i-1]].push_back(id[i]);
		}
		else id[i]=id[x];
	}
	f[0]=-1;f[1]=0;dfs(0,-1);
	for(int i=1;i<=n;i++)
		printf("%lld\n",ans[id[i]]%MOD);
}


这篇关于[HNOI2019] JOJO的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程