• BZOJ2434[NOI2011]阿狸的打字机


    知识点:

    AC自动机,fail树,离线,树状数组,主席树

    题意:

    给定若干个串,若干次询问(x,y)x串在y串中出现了多少次,除了值域(小写字母)外范围都是1e5.

    解法:

    首先,按照它给定的“打字机读入方式”,我们可以在trie上跳,模拟这一个过程。遇到小写字母,则往下跳(没有就插入新点),遇到(B)就跳回其父亲处(所以trie还要维护每个点父亲点的编号),遇到(P)就标记一下结尾的节点(假如原来有点,那么就把当前串的fa定为原来的那个串,否则新增一个编号)。假如不按上面这种方法来的话,复杂度上界是(O(n^2))的。

    接下来,考虑单个的(x,y)怎么匹配。注意题目问的是x在y中出现了多少次,我们要明确几个性质。

    • 一个ACAM上的节点到根节点上的路径,每一个路径上的点都是当前点的前缀。这个用trie来理解就非常显然了。
    • 一个fail树上的节点到根节点上的路径,每一个路径上的点都是当前点的后缀,且一定是长度依次单调递减的一个后缀。

    这里补充一下什么就做fail树,fail树就是把fail指针反向连接起来形成的一棵树,具有很多很优美的性质。

    那么我们通过以上两个性质,我们可以发现,问x在y中出现的次数,就是问x在fail树的子树中有多少个点是y节点在fail树中所对应节点的祖先(包括y自己)。

    所以我们考虑怎么维护上面的那个信息。

    假如在线做,那么每次遍历一次fail树,是(O(n^2))的,所以我们必须把所有询问离线下来,因为是问y作为文本串的,所以可以选择把y排序处理。不过这里我选择开个vector直接把y对应的fa(就是在插入时,相同的话就记的那个fa)对应的ACAM上的编号挂上去,把x也同样处理后挂上去。

    考虑把fail树的DFS序弄出来(因为以某个节点为根的子树一定是在DFS序上连续的一段),所以可以转化为序列上的问题用树状数组等数据结构来维护。因为每次进入走出某个节点发生的影响都是1,所以DFS一遍trie树(注意这里要在求fail之前把所有的son备份一遍,否则会连到一些原来没有的点上),一到一个点就把当前点在fail树上的DFS序所对应的点在树状数组上+1,然后回答当前点的所有询问(均为x点的子树右端点-x点的子树左端点对于的值),然后遍历所有的真儿子,然后-1走人。

    注意:

    1. 搞清楚什么时候是fail,什么时候是trie。这里理解子串个人认为可以类比SAM,因为一个子串=后缀的前缀=前缀的后缀,所以是遍历trie树,但是树状数组维护的DFS序是在fail树上的。
    2. 假如你在想,为什么不是加上一个地方的所有的子串结尾的个数,而是+1?是因为题目问的是x在y中出现的次数,就算出现很多次,那也只算一次。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<vector>
    using namespace std;
    
    const int maxn=100010;
    int n,m,head[maxn],etot,tot=1,c[maxn],dfn[maxn],low[maxn],fa[maxn],cnt,ans[maxn],id[maxn];
    struct node
    {
    	int nxt,to;
    }edge[maxn];
    struct trie
    {
    	int son[26],fail,tag,fa,ch[26];
    }a[maxn];
    struct pro
    {
    	int x,id;
    };
    vector<pro>que[maxn];
    queue<int>q;
    char s[maxn];
    
    int read()
    {
    	int x=0;
    	char c=getchar();
    	while (c<48||c>57)
    		c=getchar();
    	while (c>=48&&c<=57)
    		x=(x<<1)+(x<<3)+(c^48),c=getchar();
    	return x;
    }
    
    void insert(char *s)
    {
    	int i,u=1,len=strlen(s),k;
    	a[1].fa=1;
    	for (i=0;i<len;i++)
    	{
    		if (s[i]>='a'&&s[i]<='z')
    		{
    			k=s[i]-'a';
    			if (!a[u].son[k])
    			{
    				a[u].son[k]=(++tot);
    				a[tot].fa=u;
    			}
    			u=a[u].son[k];
    		}
    		if (s[i]=='B')
    			u=a[u].fa;
    		if (s[i]=='P')
    		{
    			id[++n]=u;
    			fa[n]=n;
    			if (a[u].tag)
    				fa[n]=a[u].tag;
    			else
    				a[u].tag=n;
    		}
    	}
    }
    
    void getfail()
    {
    	int i,u,v,fafail;
    	for (i=0;i<=25;i++)
    		a[0].son[i]=1;
    	a[1].fail=0;
    	q.push(1);
    	while (!q.empty())
    	{
    		u=q.front();
    		q.pop();
    		fafail=a[u].fail;
    		for (i=0;i<=25;i++)
    		{
    			v=a[u].son[i];
    			if (!v)
    				a[u].son[i]=a[fafail].son[i];
    			else
    			{
    				a[v].fail=a[fafail].son[i];
    				q.push(v);
    			}
    		}
    	}
    }
    
    void add(int u,int v)
    {
    	edge[++etot]=(node){head[u],v};
    	head[u]=etot;
    }
    
    int lowbit(int x)
    {
    	return x&-x;
    }
    
    void update(int x,int val)
    {
    	for (;x<=cnt;x+=lowbit(x))
    		c[x]+=val;	
    }
    
    int query(int x)
    {
    	int res=0;
    	for (;x;x-=lowbit(x))
    		res+=c[x];
    	return res;
    }
    
    void DFS(int u)
    {
    	int i;
    	dfn[u]=(++cnt);
    	for (i=head[u];i;i=edge[i].nxt)
    		DFS(edge[i].to);
    	low[u]=cnt;
    }
    
    void dfs(int u)
    {
    	update(dfn[u],1);
    	int i,siz=que[u].size();
    	for (i=0;i<siz;i++)
    		ans[que[u][i].id]=query(low[que[u][i].x])-query(dfn[que[u][i].x]-1);
    	for (i=0;i<=25;i++)
    		if (a[u].ch[i])
    			dfs(a[u].ch[i]);
    	update(dfn[u],-1);
    }
    
    int main()
    {
    	int i,j,u,v;
    	scanf("%s",s);
    	insert(s);
    	for (i=1;i<=tot;i++)
    		for (j=0;j<=25;j++)
    			a[i].ch[j]=a[i].son[j];
    	getfail();
    	for (i=1;i<=tot;i++)
    		if (a[i].fail!=i&&a[i].fail>=1)
    			add(a[i].fail,i);
    	DFS(1);
    	m=read();
    	for (i=1;i<=m;i++)
    	{
    		u=id[fa[read()]],v=id[fa[read()]];
    		que[v].push_back((pro){u,i});
    	}
    	dfs(1);
    	for (i=1;i<=m;i++)
    		printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    日期控件选择条件控制只能选择当前日期之前或当前日期之后
    记录一次ajax 429请求laravel api的错误
    如何配置Linux系统的IP地址?
    laravel 定时任务通过队列发送邮件
    ioutil.ReadFile 读取文件内容时为什么读取不到文件呢?open var2.go: no such file or directory
    swoole异步io操作
    PHP获取本周所有日期或者最近七天所有日期
    面试又给我问到MySQL索引,最全的一次整理
    Google资深工程师深度讲解Go语言基础语法(二)
    ps命令详解
  • 原文地址:https://www.cnblogs.com/Ronald-MOK1426/p/12236634.html
Copyright © 2020-2023  润新知