「HAOI2017」字符串 (AC自动机)(BIT)(dfs序)

L O J LOJ LOJ 传送门

  • 开题,咦?怎么做过,咦?怎么不会了
  • 题解:挺好的一道题,第一遍记不清多久写的了不过肯定是抄的。
    考虑暴力,枚举匹配位置,尽量匹配一个前缀和后缀,如果中间空缺的位置长度 ≥ k \ge k k 就有 1 的贡献
    我们对询问串极其反串建 A C AC AC 自动机,考虑对于正串一个结点,假设它代表的串的长度为 i i i,钦定 S S S 与这个正串的前 i i i 位完美匹配,那么现在需要保证后 i + k + 1 i+k+1 i+k+1 位要完美匹配。设当前节点为 u u u,反过来的 A C AC AC 自动机的 i + k + 1 i+k+1 i+k+1 代表的节点为 v v v,那么需要合法的匹配位置就是正着的第 j j j 位出现在 u u u f a i l fail fail 子树中,反过来的第 j + k + 1 j+k+1 j+k+1 位出现在 v v v 的子树中,因为要保证 i + k + 1 i+k+1 i+k+1 这个后缀是 j + k + 1 j+k+1 j+k+1 这个后缀的前缀。
  • 所以问题被我们简化了,对于正着的第 j j j 位走到的节点,我们把反过来的 A C AC AC 自动机的 j + k + 1 j+k+1 j+k+1 代表的节点点亮,如果我们将所有 u u u 子树中的对应的节点点亮后, v v v 中点亮的个数就是匹配次数, d f s dfs dfs序 + b i t bit bit
  • 但是有个小问题,令一个位置匹配尽量匹配前缀后缀后中间的长度为空缺长度 L L L,那么一个 L ≤ k L\le k Lk 会被算 k − L + 1 k-L+1 kL+1 次,我们想让其只算一次,所以我们求出 k − 1 k-1 k1 的答案容斥掉。
    但这样又会有一个头疼的问题,令前缀匹配长度为 i i i,那么次数实际上是 m i n ( i + 1 , k − L + 1 ) min(i+1,k-L+1) min(i+1,kL+1),将 k k k 置为 k − 1 k-1 k1 算可能减掉相同的量,这里需要特殊处理。
#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 4e5 + 50;
int k, n, m; char S[N];
int len[N], ch[N][95], fail[N], as[N], nd;
vector<int> G[N]; int in[N], out[N], sgn;
struct data{ 
	int c, u, v, ur, vr; 
	data(int _c = 0, int _u = 0, int _v = 0, int _ur = 0, int _vr = 0){ 
		c = _c; u = _u; v = _v; ur = _ur; vr = _vr;
	} 
};
vector<data> v[N];
vector<int> p[N],q[N];
void ins(int w, char *S){
	int len = strlen(S+1), now = 0;
	for(int i = 1; i <= len; i++){
		int c = S[i] - 33; if(!ch[now][c]) ch[now][c] = ++nd;
		now = ch[now][c];
	}
	static int ps[N]; ps[len+1] = 0; now = 0;
	for(int i = len; i >= 1; i--){
		int c = S[i] - 33; if(!ch[now][c]) ch[now][c] = ++nd;
		now = ch[now][c]; ps[i] = now;
	}
	for(int i = 0, now = 0; i + k <= len; i++){
		data nx(w, ps[i+k+1], i ? ps[i+k] : -1); 
		v[now].push_back(nx); now = ch[now][S[i+1]-33];
	}
}
void build(){
	queue<int> q;
	for(int i = 0; i <= 94; i++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int i = 0; i <= 94; i++)
		if(ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]);
		else ch[x][i] = ch[fail[x]][i];
	}
	for(int i = 1; i <= nd; i++) G[fail[i]].push_back(i);
}
void pre_dfs(int u){ in[u] = ++sgn; for(int v : G[u]) pre_dfs(v); out[u] = sgn; }
struct BIT{
	int c[N];
	void add(int x){ for(;x<=sgn;x+=x&-x) ++c[x]; }
	int ask(int x){ int as=0; for(;x;x-=x&-x) as+=c[x]; return as; }
	int qry(int l, int r){ return ask(r)-ask(l-1); }
}T[2];
void work(int u){
	for(auto &t : v[u]){
		t.ur = T[0].qry(in[t.u],out[t.u]);
		if(~t.v) t.vr = T[1].qry(in[t.v],out[t.v]);
	}
	for(auto t : p[u]) T[0].add(in[t]);
	for(auto t : q[u]) T[1].add(in[t]);
	for(int v : G[u]) work(v);
	for(auto t : v[u]){
		as[t.c] += T[0].qry(in[t.u],out[t.u]) - t.ur;
		if(~t.v) as[t.c] -= T[1].qry(in[t.v],out[t.v]) - t.vr;
	}
}
int main(){
	scanf("%d%s%d",&k,S+1,&m); n = strlen(S+1);
	for(int i = 1; i <= m; i++){
		static char s[N];
		scanf("%s",s+1); int len = strlen(s+1);
		if(len <= k){ as[i] = max(0, n-len+1); continue; } ins(i,s);
	} build();
	static int pos[N];
	for(int i = n, now = 0; i >= 1; i--) pos[i] = now = ch[now][S[i]-33];
	for(int i = 0, now = 0; i + k <= n; i++){
		p[now].push_back(pos[i+k+1]);
		q[now].push_back(pos[i+k]); now = ch[now][S[i+1]-33];
	} pre_dfs(0); work(0); 
	for(int i = 1; i <= m; i++) cout << as[i] << '\n';
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值