首页 > 其他分享 >[百紫祭] 洛谷P5840做题笔记

[百紫祭] 洛谷P5840做题笔记

时间:2023-07-26 19:12:28浏览次数:36  
标签:AC 洛谷 int top son 百紫 做题 LCA 自动机

[百紫祭] 洛谷P5840做题笔记

luogu传送门

前置芝士:AC自动机,树上差分,树剖求LCA,树状数组。

前言

一篇笔记需要一张头图。

题意

给出 \(n\) 个字符串 \(S_1,S_2\ .\ .\ .\ S_n\) 和一个初始为空的字符串集合 \(T\) ,接下来 \(q\) 次操作。操作分为修改和询问。

修改操作为向 \(T\) 中插入一个字符串 \(P\) ;查询操作为求出 \(T\) 中有多少个包含了 \(S_x\) 。对每个查询输出答案。

题解

一道AC自动机的神题,值得一做。

看到 “字符串” ,“包含” 两个字眼,可以想到一种很暴力的AC自动机做法。

具体做法是:每插入一个字符串 \(P\) ,考虑暴力跳fail指针求出所有其包含的字符串,将所有包含的字符串答案加一。查询时只要输出就行。

但是看到 \(2 \times 10^6\) 的数据范围,显然是会炸的。考虑优化。

比较主流的做法是在AC自动机上做树上差分+树剖LCA+树状数组。

考虑记录 \(P\) 在AC自动机上跳到过的所有点。学过AC自动机的都知道,AC自动机上跳到的每一个点所代表的字符串都包含在 \(P\) 内。

然后我们观察跳fail指针的过程。众所周知AC自动机的fail可以构成一棵树,那么一次更新答案就是将根节点到当前跳到的节点链上的所有点加一。

可以考虑一种思路,先求出所有点的 \(dfs\) 序,方便后续操作。

设 \(P\) 串在匹配过程中跳到了 \(u_1,u_2,u_3\ .\ . \ .\ u_l\),那么我们可以完成以下操作:

1:根据所有点的 \(dfs\) 序排序

2:对于每个节点,将 \(u_i\) 到根节点链上所有点加一。

3:对于每个点对 \((u_i,u_{i+1})\) ,将 \(LCA(u_i,u_{i+1})\) 到根节点上链上的所有点减一。(因为不能重复算。)

对于 \(LCA\) ,显然可以树链剖分求。(注:笔者尝试倍增LCA结果貌似被卡,所以临时学了更快的树剖LCA,可以顺利通过)对于链上操作,显然可以使用树上差分。那么2,3操作就可以转换成单点操作。

但是查询就又要讨论了。使用了树上差分后,查询操作从单点查变成了求子树和。

这时候前面处理的 \(dfs\) 序再次派上用场了。根据 \(dfs\) 序的性质,一个子树内所有的编号是连续的。这时候就可以用一波树状数组了。

总结一下思路,第一步建AC自动机,第二步在AC自动机上 \(dfs\) 求出 \(dfs\) 序,并预处理树剖 \(LCA\) 。第三步结合树上差分和树状数组进行修改和查询。

总复杂度 \(O(|S|\ log\ |S|)\) ,对于四秒而言已经足够。

code

//writer:Oier_szc

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N=1e5+5,M=2e6+5;
int n;
char s[N];
int tr[M][26],idx=0;
int fail[M],id[N];
inline void insert(int ID,char s[])
{
	int u=0;
	for(int i=1;i<=strlen(s+1);++i)
	{
		int to=s[i]-'a';
		if(!tr[u][to]) tr[u][to]=++idx;
		u=tr[u][to];
	}
	id[ID]=u;
}
inline void build()
{
	queue<int> q;
	for(int i=0;i<26;++i)
	{
		if(tr[0][i]) q.push(tr[0][i]);
	}
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		for(int i=0;i<26;++i)
		{
			if(!tr[now][i])
			{
				tr[now][i]=tr[fail[now]][i];	
			}	
			else
			{
				fail[tr[now][i]]=tr[fail[now]][i];
				q.push(tr[now][i]);
			}
		}
	}
}
int head[M],ne[M<<1],to[M<<1],tot=0;
inline void add(int u,int v)
{
	to[++tot]=v;
	ne[tot]=head[u];
	head[u]=tot;
}
int dfn[M],r[M],timec=0;
int top[M],son[M],sz[M],fa[M],deep[M];
inline void dfs(int u)
{
	dfn[u]=r[u]=++timec;
	sz[u]=1;
	for(int i=head[u];i;i=ne[i])
	{
		fa[to[i]]=u;
		deep[to[i]]=deep[u]+1;
		dfs(to[i]);
		sz[u]+=sz[to[i]];
		if(!son[u]||sz[to[i]]>sz[son[u]]) son[u]=to[i];
		//!son[u]必须加,不然u==0肯定出问题,T飞掉。 
		r[u]=max(r[u],r[to[i]]);
	}
}
inline void dfs2(int u,int Top)
{
	top[u]=Top;
	if(son[u]) dfs2(son[u],Top);
	for(int i=head[u];i;i=ne[i])
	{
		if(to[i]==son[u]) continue;
		dfs2(to[i],to[i]);
	}
}
inline int lca(int a,int b)
{
	while(top[a]!=top[b])
	{
		if(deep[top[a]]>deep[top[b]]) a=fa[top[a]];
		else b=fa[top[b]];	
	}
	return deep[a]<deep[b]?a:b;
}
int tree[M];
int v[M];
inline int lowbit(int u)
{
	return u&(-u);
}
inline void update(int u,int x)
{
	for(int i=u;i<=timec;i+=lowbit(i))
	{
		tree[i]+=x;
	}
}
inline int query(int u)
{
	int res=0;
	for(int i=u;i!=0;i-=lowbit(i))
	{
		res+=tree[i];
	}
	return res;
}
inline bool cmp(int a,int b)
{
	return dfn[a]<dfn[b];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",s+1);
		insert(i,s);
	}
	build();
	for(int i=1;i<=idx;++i)
	{
		add(fail[i],i);
	}
	dfs(0);
	dfs2(0,0);
	int q;
	scanf("%d",&q);
	int op,x;
	while(q--)
	{
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%s",s+1);
			int p=0;
			int len=strlen(s+1);
			for(int i=1;i<=len;++i)
			{
				int to=s[i]-'a';
				p=tr[p][to];
				v[i]=p;
			}
			sort(v+1,v+1+len,cmp);
			for(int i=1;i<=len;++i)
			{
				update(dfn[v[i]],1);
			}
			for(int i=1;i<=len-1;++i)
			{
				update(dfn[lca(v[i],v[i+1])],-1);
			}
		}
		else
		{
			scanf("%d",&x);
			printf("%d\n",query(r[id[x]])-query(dfn[id[x]]-1));
		}
	}
	return 0;
}

标签:AC,洛谷,int,top,son,百紫,做题,LCA,自动机
From: https://www.cnblogs.com/StevenZC/p/17581740.html

相关文章

  • 洛谷 P2894 [USACO08FEB] Hotel G 题解
    题目链接P2894[USACO08FEB]HotelG-洛谷|计算机科学教育新生态(luogu.com.cn)分析考虑用线段树维护区间信息维护sum(最大连续空房间数)如何合并?sum1为max(sum2,sum3)(1的两个子区间)但我们发现若区间为100001(0表示空房间)sum1=4而max(sum2,sum3)=2所以再维护suml(从左开始的......
  • 【2021考研】政治做题策略
    【2021考研】政治做题策略前言回顾了一下考研政治的马克思、毛中特、史纲、思修、时政的内容,现在借助2021考试大纲以及往年考试试题猜测一下最优的做题策略。越来越发现学习就像编程序。学一门课程就是编一个程序,遇到什么问题就是:输入内容,然后处理,最后输出。在考研政治中就是输入试......
  • 洛谷 P9221 「TAOI-1」Pentiment 题解
    Description给定\(n\timesm\)的矩阵,从第\(1\)行任意格子出发,每次向下、左、有走一步,有\(q\)个障碍不能经过,求走到第\(n\)行任意格子的方案。对于所有数据,\(1\leqn,m\leq10^9\),\(1\leqq\leq10^5\)。link:https://www.luogu.com.cn/problem/P9221Solution算法一考......
  • 洛谷 P3291 [SCOI2016] 妖怪
    设每只怪物经过环境影响后的攻击力和防守力分别为\(x_i,y_i\),则有:\(y_i=dnf_i-\dfracba(x_i-atk_i)\)。设\(k=-\dfracba\),则有\(y_i=kx_i+dnf_i-k\cdotatk_i\)。设直线\(l_i:y_i=kx_i+dnf_i-k\cdotatk_i\),第\(i\)只怪物在\((a,b)\)的环境下......
  • 洛谷P3629 [APIO2010] 巡逻题解
    题目链接P3629[APIO2010]巡逻-洛谷|计算机科学教育新生态(luogu.com.cn)思路n个村庄,n-1条道路,原图为树1.若k=0(不修建道路)那么答案为(n-1)*2 每个道路会走两遍2.若k为1(修建一条道路)设修建的道路(r1)所在的环长度为L那么答案为(n-1)*2-L+2可以看到r1所在环的道路只走了......
  • 洛谷CF1738C题解
    好一道博弈论水题题目传送门更好的食用体验题目大意:给定长度为$n$的数列$a_1,a_2,\dots,a_n$,两名玩家Alice、Bob依次以最优策略从数列中取走一个数,Alice先取,直至为空博弈结束。若Alice取走的所有数之和为偶,Alice胜利;若Alice取走的所有数之和为奇,Bob胜利......
  • 洛谷 T356695 文字处理软件(重置版)
    很简单了啊!说普及-我都不信作者(也就是我)链接:https://www.luogu.com.cn/problem/T356695好好想想!!!!题目!文字处理软件(重置版)题目背景Allow是一名程序员,他要为公司开发一款“文字处理软件”!题目描述用户可能输入∞个数字。说白了用while(1)输入1时,把字符串原样输出。......
  • 洛谷AT_jsc2019_qual_e Card Collector 题解
    题目链接CardCollector-洛谷|计算机科学教育新生态(luogu.com.cn)思路将每一行、每一列转化为点,第i行第j列的卡牌转化为i->j+m(m为行数)的有向边。总共会抽取m+n(m为行数,n为列数)张牌,每个点的出度为1。结果图为基环森林;那么题目就转化为求最大基环森林。代码1#include......
  • 7.23 做题记录
    P3897[湖南集训]CrazyRabbit考虑一个点到圆的两个切点构成的圆弧,如果两个点连线与圆不交,那么两端圆弧是相交但不包含的,且把一段圆弧取反不影响答案。断环成链,原问题等价于选最多的区间\((l,r)\)满足对于所有\(i<j\),\(l_j<r_i\)枚举起始区间做LCS即可,时间复杂度\(O(......
  • 洛谷3294 背单词
    这题乍一看是排序贪心,然后使用领项交换来做题由于有了第一条规则的存在,因为\(n*n\)远大于另外两条规则所产生的代价,所以我们不会让后缀排在后面于是乎,我们倒序建立trie树并且重构树(具体可见洛谷题解),那么问题就转换为给这棵树标号,要求必须标了父亲才能标儿子,令每一条边的代价为......