西雅图
18:14发布
西雅图
18:14发布
8°
东南风 4级
空气质量 无
相对湿度 90%
今天
中雨
6°/11°
周四
中雨
3°/10°
周五
2°/10°

洛谷P5840 [COCI2015]Divljak

我的blog

题目链接:洛谷P5840 [COCI2015]Divljak

vijos上我的个人域题题目链接:Divljak

preface

本文版权归博客园,洛谷博客和蒟蒻wjr所有,欢迎转载,但需保留此段声明,并给出原文链接,如有侵权行为,还请不吝啬向博主举报,谢谢合作。

一道AC自动机+LCA+树链剖分+树上差分+树状数组维护的恶心题

description

首先理解trie,树上任意一个节点到跟节点是插入串的前缀,而nxt指针是指向这个前缀的后缀。

我们建一棵关于这样的树,一个节点的子树上的所有节点到根都是把其这个节点当做后缀,那我们就建一棵树。

先将S串集合建一个AC自动机

然后将(nxt[u],u)插入这条树(是另建一棵树,不是AC自动机的Trie树)的边,显然如果nxt[u]=0连向的是根节点
(我的代码根节点是0,所以不做判断)。

接着再将建好的那棵树求出每个节点的dfs序。

我们用到树上差分的思想,然后把要插入的T集合在Trie树中统计走过的节点,记录下来,再按dfn排序,将相邻两个节点的在树上的位置+1,表示多一个串匹配,但是他们的LCALCA的祖先很明显是+2串匹配,不符合我们此串在T集合中的出现次数,所以将两个节点的LCA的位置标记1

对于此过程,我们可以用树状数组统计答案,将此树树链剖分后,按每个节点的dfs序维护,同时也可以跑LCA,则S串在T集合中的出现次数为其节点和其子树的之和。

code

#include <cstdio>
#include <queue>
#include <cassert>
#include <algorithm>
#define re register
using namespace std;
const int N=2e6+5,M=1e5+5;
int ch[N][26],nxt[N],tot;
int pos[M];
inline void insert(char *s,int id)
{
	int u=0;
	for(re int i=0; s[i]; ++i)
	{
		int c=s[i]-'a';
		if(!ch[u][c])
			ch[u][c]=++tot;
		u=ch[u][c];
	}
	pos[id]=u;
}
struct Edge
{
	int next,to;
} edge[N];
int head[N],num_edge;
inline void add_edge(int from,int to)
{
	edge[++num_edge].next=head[from];
	edge[num_edge].to=to;
	head[from]=num_edge;
}
inline void build()
{
	queue<int>q;
	for(re int i=0; i<26; ++i)
		if(ch[0][i])
			q.push(ch[0][i]);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(re int i=0; i<26; ++i)
			if(!ch[u][i])
				ch[u][i]=ch[nxt[u]][i];
			else
			{
				q.push(ch[u][i]);
				nxt[ch[u][i]]=ch[nxt[u]][i];
			}
	}
}
int fa[N],top[N],dep[N],size[N],son[N];
inline void dfs1(int u,int fa_)
{
	fa[u]=fa_;
	size[u]=1;
	dep[u]=dep[fa_]+1;
	for(re int i=head[u]; i; i=edge[i].next)
	{
		int &v=edge[i].to;
		dfs1(v,u);
		size[u]+=size[v];
		if(!son[u]||size[v]>size[son[u]])
			son[u]=v;
	}
}
int dfn[N],dfstime;
inline void dfs2(int u,int topf)
{
	top[u]=topf;
	dfn[u]=++dfstime;
//	printf("%d ",u);
	if(!son[u])return;
	dfs2(son[u],topf);
	for(re int i=head[u]; i; i=edge[i].next)
	{
		int &v=edge[i].to;
		if(v==son[u])
			continue;
		dfs2(v,v);
	}
}
inline int LCA(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return x;
}
int n,q;
char str[N];
int a[N];
int lowbitsum[N];
#define lowbit(x) (x&(-x))
inline void update(int x,int y)
{
//	assert(x>=1);
	for(; x<=dfstime; x+=lowbit(x))
		lowbitsum[x]+=y;
}
inline int query(int x)
{
//	assert(x>=1);
	int res=0;
	for(; x; x-=lowbit(x))
		res+=lowbitsum[x];
	return res;
}
inline bool cmp(const int &x,const int &y)
{
	return dfn[x]<dfn[y];
}
inline void solve1()
{
	int u=0,tp=0;
	for(re int i=0; str[i]; ++i)
	{
		u=ch[u][str[i]-'a'];
		a[++tp]=u;
	}
//	for(re int i=1; i<=tp; ++i)
//		printf("%d ",a[i]);
	sort(a+1,a+1+tp,cmp);
	bool flag=false;
	for(re int i=1; i<=tp; ++i)
	{
		update(dfn[a[i]],1);
		if(flag)
			update(dfn[LCA(a[i],a[i-1])],-1);
		else
			flag=true;
	}
}
inline void solve2()
{
	int x;
	scanf("%d",&x);
	printf("%d\n",query(dfn[pos[x]]+size[pos[x]]-1)-query(dfn[pos[x]]-1));
}
int main()
{
	scanf("%d",&n);
	for(re int i=1; i<=n; ++i)
	{
		scanf("%s",str);
		insert(str,i);
	}
	build();
//	for(re int i=1; i<=tot; ++i)
//		printf("%d %d\n",nxt[i],i);
	for(re int i=1; i<=tot; ++i)
		add_edge(nxt[i],i);
//	printf("%d\n",num_edge);
	dfs1(0,tot+1);
	dfs2(0,0);
//	printf("%d\n",dfstime);
//	for(re int i=0; i<=tot; ++i)
//		printf("%d ",dfn[i]);
	scanf("%d",&q);
	while(q--)
	{
		int opt;
		scanf("%d",&opt);
		if(opt==1)
		{
			scanf("%s",str);
			solve1();
		}
		else if(opt==2)
			solve2();
	}
	return 0;
}

作者:蒟蒻wjr
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

posted @   蒟蒻wjr  阅读(285)  评论(1编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?

点击右上角即可分享
微信分享提示