CodeForces - 1207G Indie Album

题意:

给定 n n n 个字符串,产生方式为 ① 1 , c 1, c 1,c,表示 s i s_i si 是一个字符 c c c;② 2 , j , c 2, j, c 2,j,c,表示 s i s_i si s j + c s_j + c sj+c。再有 m m m 次询问,每次询问给出 i , t i, t i,t,表示求串 t t t 在串 s i s_i si 中出现的次数。 ( n , m , ∑ ∣ s i ∣ , ∑ ∣ t i ∣ ≤ 4 × 1 0 5 ) (n, m, \sum |s_i|, \sum |t_i| \leq 4×10^5) (n,m,si,ti4×105)

链接:

https://codeforces.com/problemset/problem/1207/G

解题思路:

多模式串、多主串的匹配,显然主串 s i s_i si 需要用字典树存储,一方面可以考虑广义后缀自动机,以 s i s_i si 建立自动机,那么模式串 t i t_i ti 在自动机上找到对应结点,其 r i g h t right right 集合中在串 s i s_i si 中的 e n d p o s endpos endpos 数量即为答案,但是 r i g h t right right 集合总大小可以是 O ( n 2 ) O(n^2) O(n2) 的。反向考虑每个主串结点对模式串的贡献,那么就是跑主串,每次父链上的模式串计数 + 1 +1 +1 ,最后查询每个模式串对应结点的计数情况就是答案。

但是显然也不能直接枚举每个主串 s i s_i si 去自动机上跑,由于每个主串 s i s_i si 都是其字典树上的一条链,那么对字典树进行 d f s dfs dfs,每次到主串结束结点时统计它对相应的询问的模式串的贡献,回溯时撤回父链上的计数,那么只需要枚举字典树的结点数有关。

然后我们发现不如直接对模式串建立 A C AC AC 自动机。

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 4e5 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

char s[maxn];
vector<pii> P[maxn];
vector<int> G[maxn], qr[maxn];
int nxt[maxn][26], fail[maxn], pos[maxn];
int dfn[maxn], siz[maxn], c[maxn], ans[maxn];
int n, m, cnt, tim;

#define lowb(x) ((x)&(-x))
void update(int x, int val){

	if(x <= 0) return;
	while(x <= tim) c[x] += val, x += lowb(x);
}

int query(int x){

	int ret = 0;
	while(x) ret += c[x], x -= lowb(x);
	return ret;
}

int add(){

	++cnt; fail[cnt] = 0; return cnt;
}

void init(){

	cnt = -1; add();
}

int insert(char *s){

	int p = 0;
	while(*s){
		
		int t = *s - 'a';
		if(!nxt[p][t]) nxt[p][t] = add();
		p = nxt[p][t];
		++s;
	}
	return p;
}

void cFail(){

	queue<int> q;
	for(int i = 0; i < 26; ++i) if(int v = nxt[0][i]) q.push(v);
	while(!q.empty()){

		int u = q.front(); q.pop();
		for(int i = 0; i < 26; ++i){

			if(int v = nxt[u][i]) fail[v] = nxt[fail[u]][i], q.push(v);
			else nxt[u][i] = nxt[fail[u]][i];
		}
	}
}

void dfs(int u){

	dfn[u] = ++tim, siz[u] = 1;
	for(auto &v : G[u]){

		dfs(v);
		siz[u] += siz[v];
	}
}

void build(){

	cFail();
	for(int i = 1; i <= cnt; ++i) G[fail[i]].pb(i);
	dfs(0);
}

void dfs(int u, int p){

	for(auto &e : P[u]){

		int v = e.second, ch = e.first;
		int np = nxt[p][ch - 'a'];
		update(dfn[np], 1);
		for(auto &id : qr[v]){

			int x = pos[id];
			int l = dfn[x], r = dfn[x] + siz[x] - 1;
			ans[id] = query(r) - query(l - 1);
		}
		dfs(v, np);
		update(dfn[np], -1);
	}
}

int main() {

	ios::sync_with_stdio(0); cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; ++i){

		int opt, j; char ch[2]; cin >> opt;
		if(opt == 1){

			cin >> ch;
			P[0].pb({ch[0], i});
		}
		else{

			cin >> j >> ch;
			P[j].pb({ch[0], i});
		}
	}
	cin >> m;
	init();
	for(int i = 1; i <= m; ++i){

		int p; cin >> p >> s;
		pos[i] = insert(s);
		qr[p].pb(i);
	}
	build();
	dfs(0, 0);
	for(int i = 1; i <= m; ++i){

		cout << ans[i] << "\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值