Kruskal重构树+AC自动机+树状数组:Gym - 104542F

https://vjudge.net/contest/579844#problem/F

  1. 看到连边和没有强制在线,考虑Kruskal重构树
  2. 看到判断子串,考虑AC自动机+线段树

然后要非常大胆地把两个结合起来。

然后就是大码量了。

具体总结一下流程:

  1. 先建出Kruskal重构树
  2. 对Kruskal重构树处理出dfn序
  3. 重新对字符串进行排序
  4. 对字符串建AC自动机
  5. 对fail树处理出dfn序
  6. 对询问操作处理出Kruskal重构树上对应区间进行差分
  7. 按顺序加入每个字符串,在树状数组上进行区间加操作,树状数组维护差分
  8. 对于每个询问在Trie树上跑,在对应树状数组上询问并统计答案
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar(); while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
#define N 500010
struct node {
	int id, f; 
};
int n, m, i, j, k, T;
int a[N], a_st[N], a_ed[N], num, tot; 
int dfn[N], ed[N], p[N], qqq, f[N], op, dian[N]; 
int ans[N]; 
int u, v; 
vector<int>G[N]; 
vector<node>Q[N]; 
vector<int>s[N], t[N], S[N]; 
char str[N]; 
queue<int>q; 

int fa(int x) {
	if(f[x]==x) return x; 
	return f[x]=fa(f[x]); 
}

void dfs(int x) {
	if(x<=n) a[++num]=x, a_st[x]=num; 
	for(int y :  G[x]) {
		dfs(y); 
		if(!a_st[x]) a_st[x]=a_st[y]; 
	}
	a_ed[x]=num; 
}

struct Tree_zhuang_number_group {
	int n, cnt[N]; 
	void add(int x, int y) {
		while(x<=n)	 {
			cnt[x]+=y; x+=x&-x; 
		}
	}
	int que(int x) {
		int ans=0; 
		while(x) ans+=cnt[x], x-=x&-x; 
		return ans; 
	}
}Bin;

struct AC_auto_machine {
	int tot=1, nxt[N][26], fail[N]; 
	int dfn[N], ed[N], num; 
	vector<int>G[N]; 
	int Trie(int u, int i, int id) {
		if(!s[id][i]) return u; 
		int c=s[id][i]-'a'; 
		if(!nxt[u][c]) nxt[u][c]=++tot; 
		return Trie(nxt[u][c], i+1, id); 
	}
	void bfs() {
		q.push(1); 
		while(!q.empty()) {
			u=q.front(); q.pop(); 
			for(int i=0; i<26; ++i) {
				if(!nxt[u][i]) continue; 
				v=nxt[u][i]; k=fail[u]; 
				while(k && !nxt[k][i]) k=fail[k]; 
				if(nxt[k][i]) fail[v]=nxt[k][i]; 
				else fail[v]=1; 
				q.push(v); 
			}
		}
	}
	void pre_dfs() {
		for(int i=2; i<=tot; ++i) G[fail[i]].pb(i); 
	}
	void dfs(int x) {
		dfn[x]=++num;
		for(int y : G[x]) dfs(y); 
		ed[x]=num; 
	}
	void jia(int i) {
		Bin.add(ed[p[i]]+1, -1); 
		Bin.add(dfn[p[i]], 1); 
	}
	int que(int id) {
		char c; 
		int ans=0; 
		for(int j=1, i=0; t[id][i]; ++i) {
			c=t[id][i]-'a'; 
			while(j && !nxt[j][c]) j=fail[j]; 
			if(nxt[j][c]) j=nxt[j][c]; 
			else j=1; 
			ans+=Bin.que(dfn[j]); 
		}
		return ans; 
	}
}AC;

signed main()
{
//	freopen("in.txt", "r", stdin);
//	freopen("out.txt", "w", stdout);
	n=read(); 
	for(i=1; i<=n; ++i) {
		scanf("%s", str+1); 
		for(j=1; str[j]; ++j) S[i].pb(str[j]); S[i].pb(0); 
	}
	qqq=read(); tot=n; 
	for(i=1; i<=2*n; ++i) f[i]=i; 
	while(qqq--) {
		op=read(); 
		if(op==1) {
			u=read(); v=read(); 
			u=fa(u); v=fa(v); 
			if(u==v) continue; 
			++tot; G[tot].pb(u); G[tot].pb(v); 
			f[u]=tot; f[v]=tot; 
		}
		else {
			u=read(); scanf("%s", str+1); 
			u=fa(u); 
			++m; dian[m]=u; 
			for(j=1; str[j]; ++j) t[m].pb(str[j]); t[m].pb(0); 
		}
	} 
	for(i=1; i<=tot; ++i) if(fa(i)==i) G[tot+1].pb(i), f[i]=tot+1; 
	++tot; 
	dfs(tot); 
	for(i=1; i<=n; ++i) s[i]=S[a[i]]; 
	for(i=1; i<=n; ++i) {
		p[i]=AC.Trie(1, 0, i); 
	}
	AC.bfs(); 
	AC.pre_dfs(); 	
	AC.dfs(1); 
	Bin.n=AC.tot; 
	for(i=1; i<=m; ++i) {
		Q[a_st[dian[i]]-1].pb({i, -1}); 
		Q[a_ed[dian[i]]].pb({i, 1}); 
	}
	for(i=1; i<=n; ++i) {
		AC.jia(i);
		for(node x : Q[i]) {
			ans[x.id]+=x.f*AC.que(x.id); 
		}
	}
	for(i=1; i<=m; ++i) printf("%lld\n", ans[i]); 
	return 0;
}

``
相关推荐
XuYueming1 天前
[NOIP2022] 比赛 随机排列 部分分
数学·线段树·题解·单调栈·洛谷·扫描线·二维数点·部分分·概率 & 期望
summ1ts19 天前
NOIP2023题解
数据结构·c++·算法·线段树·动态规划·图论·noip2023
LK自动机1 个月前
【学习笔记】kruskal重构树
kruskal重构树
ganjiee00071 个月前
leetcode|刷算法 线段树原理以及模板
算法·leetcode·线段树
闻缺陷则喜何志丹2 个月前
【线段树】2569. 更新数组后处理求和查询
c++·算法·线段树·力扣·求和·数组·查询
Qres8213 个月前
8.22 万灵药(SAM + Trie + 树剖 + 线段树)
线段树·sam·trie·树剖
Dearingxxx4 个月前
前缀和数组 差分数组
算法·前缀和·差分
闻缺陷则喜何志丹5 个月前
【区间合并 差分 栈】3169. 无需开会的工作日
c++·算法·力扣··差分·日期·区间合并
无名之逆5 个月前
3072. 将元素分配到两个数组中 II Rust 线段树 + 离散化
开发语言·算法·rust·线段树·二分·树状数组·离散化
knoci5 个月前
算法-扫描线
算法·线段树