[bzoj2434][Noi2011]阿狸的打字机——AC自动机

题目大意:

给定n个字符串,每次询问第x个字符串在第y个字符串中出现了多少次。

思路:

显然我们需要先把AC自动机给建出来。
考虑如何最暴力地计算第x个字符在第y个字符中出现了多少次,我们可以在Trie上暴力跳y的每一个节点,然后对于y的每一个节点跳fail,如果跳到了x串的结尾,那么答案+1。
这样对于每一个节点都跳fail显然复杂度无法接受,那么换一个角度考虑,x的结尾可以被y串上的多少个节点给跳到。
于是我们可以把每个节点的fail看成是它的父亲,然后问题就转化为了求x的结尾点的子树中包含了多少个y的节点,这样以后可以暴力标记y上的每一个节点,对于x直接子树求和即可。
这样以后,对于y相同的二元组就可以同时处理了,相当于把x挂在了y上。
但是这样复杂度还是太高,无法接受,于是我们发现复杂度主要在暴力标记y上的每一个节点上,于是考虑将询问按照y离线,把y挂在串y的结尾点上,然后按照dfs的顺序遍历Trie树上的每一个节点,进入这个节点时打上标记,回溯时将标记撤回,这样到了y的结尾的时候,不难发现此时打上标记的点恰好是y串上所有的点。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj2434.in","r",stdin);
	freopen("bzoj2434.out","w",stdout);
}

template<typename T>void read(T &_){
	_=0; T f=1; char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
	_*=f;
}

const int maxn=1e5+10;
int m;

int ch[maxn][26],num[maxn],fail[maxn],cnt;
int fa[maxn],le[maxn],pos[maxn];
vector<int>rank[maxn];

void print(int u){
	if(fa[u]!=1)print(fa[u]);
	putchar(le[u]+'a');
}

void init(){
	fail[cnt=1]=1;

	char s[maxn];
	scanf("%s",s+1);

	int len=strlen(s+1),u=1,c,cnt_rank=0;
	REP(i,1,len){
		if(s[i]=='B')u=fa[u];
		else if(s[i]=='P'){
			++num[u];
			rank[u].pb(++cnt_rank);
			pos[cnt_rank]=u;
		}
		else{
			c=s[i]-'a';
			if(!ch[u][c])ch[u][c]=++cnt;
			fa[ch[u][c]]=u,le[ch[u][c]]=c;
			u=ch[u][c];
		}
	}
}

void build_fail(){ int h=1,t=0,q[maxn];
	REP(i,0,25)if(ch[1][i]){
		fail[ch[1][i]]=1;
		q[++t]=ch[1][i];
	}

	while(h<=t){
		int u=q[h++];
		REP(i,0,25)if(ch[u][i]){
			int v=ch[u][i],p=fail[u];
			q[++t]=v;
			while(p!=1 && !ch[p][i])p=fail[p];
			if(ch[p][i])fail[v]=ch[p][i];
			else fail[v]=1;
		}
	}
}

int beg[maxn],to[maxn],las[maxn],cnte,dfn[maxn],cnt_dfn,sz[maxn];

void add(int u,int v){
	las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v;
}

void dfs_dfn(int u){
	dfn[u]=++cnt_dfn;
	sz[u]=1;
	for(int i=beg[u];i;i=las[i]){
		dfs_dfn(to[i]);
		sz[u]+=sz[to[i]];
	}
}

struct BIT{
	int sum[maxn];
	int lowbit(int x){
		return x&(-x);
	}
	void modify(int p,int x){
		for(;p<=cnt;p+=lowbit(p))sum[p]+=x;
	}
	int query(int p){
		int ret=0;
		for(;p>=1;p-=lowbit(p))ret+=sum[p];
		return ret;
	}
}T;

int ans[maxn];
vector<pii>qu[maxn];

void solve(int u){
	T.modify(dfn[u],1);

	REP(i,0,rank[u].size()-1){
		int y=rank[u][i];
		REP(j,0,qu[y].size()-1){
			int x=pos[qu[y][j].fi];
			ans[qu[y][j].se]=T.query(dfn[x]+sz[x]-1)-T.query(dfn[x]-1);
		}
	}

	REP(i,0,25)if(ch[u][i]){
		int v=ch[u][i];
		solve(v);
	}
	T.modify(dfn[u],-1);
}

int main(){
	File();

	init();

	build_fail();

	REP(i,2,cnt)add(fail[i],i);
	dfs_dfn(1);

	int x,y;
	read(m);
	REP(i,1,m){
		read(x),read(y);
		qu[y].pb(mk(x,i));
	}

	solve(1);
	REP(i,1,m)printf("%d\n",ans[i]);

	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
旅游社交小程序功能有管理员和用户。管理员有个人中心,用户管理,每日签到管理,景点推荐管理,景点分类管理,防疫查询管理,美食推荐管理,酒店推荐管理,周边推荐管理,分享圈管理,我的收藏管理,系统管理。用户可以在微信小程序上注册登录,进行每日签到,防疫查询,可以在分享圈里面进行分享自己想要分享的内容,查看和收藏景点以及美食的推荐等操作。因而具有一定的实用性。 本站后台采用Java的SSM框架进行后台管理开发,可以在浏览器上登录进行后台数据方面的管理,MySQL作为本地数据库,微信小程序用到了微信开发者工具,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得旅游社交小程序管理工作系统化、规范化。 管理员可以管理用户信息,可以对用户信息添加修改删除。管理员可以对景点推荐信息进行添加修改删除操作。管理员可以对分享圈信息进行添加,修改,删除操作。管理员可以对美食推荐信息进行添加,修改,删除操作。管理员可以对酒店推荐信息进行添加,修改,删除操作。管理员可以对周边推荐信息进行添加,修改,删除操作。 小程序用户是需要注册才可以进行登录的,登录后在首页可以查看相关信息,并且下面导航可以点击到其他功能模块。在小程序里点击我的,会出现关于我的界面,在这里可以修改个人信息,以及可以点击其他功能模块。用户想要把一些信息分享到分享圈的时候,可以点击新增,然后输入自己想要分享的信息就可以进行分享圈的操作。用户可以在景点推荐里面进行收藏和评论等操作。用户可以在美食推荐模块搜索和查看美食推荐的相关信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值