• [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);
    }
    
  • 相关阅读:
    IDEA、Eclipse快捷键对比
    linux常见命令
    拷贝本地文件到远程服务器的批处理脚本
    关系型数据库的超键、候选键、主键
    JAVA运算符总结
    JAVA原码反码补码
    JPA查询语句(转载)
    Spring Data JPA初使用(转载)
    android学习————项目导入常见错误整理(转载)
    Spring 系列: Spring 框架简介
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16368947.html
Copyright © 2020-2023  润新知