[AC自动机] AC自动机 从基础到进阶

做完一个题单,留念!

T1 Censoring G

Censoring S一样
考虑用栈

#include<bits/stdc++.h>
#define in Read()
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=1e5+10;
char s[NNN],t[NNN];
int n,sz;
struct Trie{
	int s[26];
	int val,fail,last;
}ch[NNN];
int sta[NNN],top;
int rt[NNN];//记录栈内元素对应trie里的节点 

inline void update(){
	int p=0,len=strlen(t+1);
	for(int i=1;i<=len;++i){
		int c=t[i]-'a';
		if(!ch[p].s[c]) ch[p].s[c]=++sz;
		p=ch[p].s[c];
	}
	ch[p].val=len;
}

inline void get_fail(){
	queue<int>q;
	int p=0;
	for(int i=0;i<=26;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
	}
	
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
			ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
		}
	}
}

inline void query(){
	int ans=0,len=strlen(s+1),p=0;
	for(int i=1;i<=len;++i){
		int c=s[i]-'a';
		p=ch[p].s[c];
		rt[i]=p;
		sta[++top]=i;
		if(ch[p].val){
			top-=ch[p].val;
			if(!top) p=0;
			else p=rt[sta[top]];
		}
	}
}

int main(){
	scanf("%s",s+1);
	n=in;
	for(int i=1;i<=n;++i){
		scanf("%s",t+1);
		update();
	}
	get_fail();
	query();
	for(int i=1;i<=top;++i)
		putchar(s[sta[i]]);
	return 0;
}

那个

  if(!top) p=0;
  else p=rt[sta[top]];

加不加无所谓,加要快一点

T2 Word

多次搜索肯定炸
考虑把文章拼在一块,单词之间用奇怪的字符分开

注意判重

#include<bits/stdc++.h>
#define in Read()
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=1e7+10;
int n,sz,lt;
char s[NNN],t[NNN];
struct Trie{
	int s[26];
	int fail,last,id;
}ch[NNN];
int ans[NNN],same[NNN];

inline void update(int id){
	int p=0,len=strlen(s+1);
	for(int i=1;i<=len;++i){
		int c=s[i]-'a';
		if(!ch[p].s[c]) ch[p].s[c]=++sz;
		p=ch[p].s[c];
		t[++lt]=s[i];
	}
	if(ch[p].id) same[id]=ch[p].id;
	else ch[p].id=id;
	t[++lt]='#';
	return;
}

inline void get_fail(){
	queue<int>q;
	int p=0;
	for(int i=0;i<26;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
			ch[p].last=ch[ch[p].fail].id?ch[p].fail:ch[ch[p].fail].last;
		}
	}
}

inline void query(){
	int p=0;
	for(int i=1;i<=lt;++i){
		int c=t[i]-'a';
		p=ch[p].s[c];
		int tmp=0;
		if(ch[p].id) tmp=p;
		else if(ch[p].last) tmp=ch[p].last;
		while(tmp){
			++ans[ch[tmp].id];
			tmp=ch[tmp].last;
		}
	}
}

int main(){
	n=in;
	for(int i=1;i<=n;++i){
		scanf("%s",s+1);
		update(i);
	}
	get_fail();
	query();
	for(int i=1;i<=n;++i){
		if(!same[i])printf("%d\n",ans[i]);
		else printf("%d\n",ans[same[i]]);
	}
	return 0;
}

T3 Video Game G

ACMDP
常见套路:设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示长度为i,末节点在ACM上节点j的最佳答案
d p [ i ] [ s o n [ p ] ] = max ⁡ { d p [ i − 1 ] [ p ] + v a l [ s o n [ p ] ] } dp[i][son[p]]=\max\{dp[i-1][p]+val[son[p]]\} dp[i][son[p]]=max{dp[i1][p]+val[son[p]]}
注意fail树上下放val
注意枚举的范围,从根节点开始

#include<bits/stdc++.h>
#define in Read()
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=1e3+10;
const int INF=1e9;
int n,k,sz,res;
char s[NNN];
struct Trie{
	int s[3];
	int val,fail,last;
}ch[NNN];
int dp[NNN][NNN];

inline void update(){
	int p=0,len=strlen(s+1);
	for(int i=1;i<=len;++i){
		int c=s[i]-'A';
		if(!ch[p].s[c]) ch[p].s[c]=++sz;
		p=ch[p].s[c];
	}
	++ch[p].val;
}

inline void get_fail(){
	queue<int>q;
	int p=0;
	for(int i=0;i<3;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
		ch[p].fail=ch[p].last=0;
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<3;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
			ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
		}
		ch[u].val+=ch[ch[u].fail].val;
	}
}

inline void query(){
	for(int i=0;i<=k;++i)
		for(int j=0;j<=sz;++j)
			dp[i][j]=-INF;
	dp[0][0]=0;
	for(int len=1;len<=k;++len)
		for(int p=0;p<=sz;++p)
			for(int j=0;j<3;++j)
				dp[len][ch[p].s[j]]=max(dp[len][ch[p].s[j]],dp[len-1][p]+ch[ch[p].s[j]].val);
}

int main(){
	n=in,k=in;
	for(int i=1;i<=n;++i){
		scanf("%s",s+1);
		update();
	}
	get_fail();
	query();
	for(int i=1;i<=sz;++i)
		res=max(res,dp[k][i]);
	printf("%d\n",res);
	return 0;
}

T4 VIDEO - Video game combos

跟T3没什么区别,双倍经验吧(复制粘贴打法好)

T5 文本生成器

考虑ACMDP
d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1]表示文章长 i i i,末节点为 j j j,不可读或可读的文章
易知 p → s o n [ p ] p\to son[p] pson[p]每一个字母都会覆盖到
考虑转移

  • 对于单词末节点
    d p [ l e n ] [ s o n [ p ] ] [ 1 ] = ∑ d p [ l e n − 1 ] [ p ] [ 0 ] + d p [ l e n − 1 ] [ p ] [ 1 ] dp[len][son[p]][1]=\sum dp[len-1][p][0]+dp[len-1][p][1] dp[len][son[p]][1]=dp[len1][p][0]+dp[len1][p][1]
  • 对于其他节点
    d p [ l e n ] [ s o n [ p ] ] [ 0 ] = ∑ d p [ l e n − 1 ] [ p ] [ 0 ] d p [ l e n ] [ s o n [ p ] ] [ 1 ] = ∑ d p [ l e n − 1 ] [ p ] [ 1 ] dp[len][son[p]][0]=\sum dp[len-1][p][0]\\ dp[len][son[p]][1]=\sum dp[len-1][p][1] dp[len][son[p]][0]=dp[len1][p][0]dp[len][son[p]][1]=dp[len1][p][1]
#include<bits/stdc++.h>
#define in Read()
#define int long long
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=2e4+10;
const int MOD=1e4+7;
int n,m,sz;
char s[NNN];
struct Trie{
	int s[26];
	int fail,last,val;
}ch[NNN];
int dp[200][NNN][2],ans;

inline void update(){
	int p=0,len=strlen(s+1);
	for(int i=1;i<=len;++i){
		if(!ch[p].s[s[i]-'A']) ch[p].s[s[i]-'A']=++sz;
		p=ch[p].s[s[i]-'A'];
	}
	ch[p].val=1;
}

inline void get_fail(){
	queue<int>q;
	int p=0;
	for(int i=0;i<26;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
			ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
		}
		ch[u].val|=ch[ch[u].fail].val;
	}
}

inline void query(){
	memset(dp,0,sizeof(dp));
	dp[0][0][0]=1;
	for(int i=1;i<=m;++i)
		for(int j=0;j<=sz;++j)
			for(int k=0;k<26;++k){
				if(ch[ch[j].s[k]].val)
					dp[i][ch[j].s[k]][1]=(dp[i][ch[j].s[k]][1]+dp[i-1][j][0]+dp[i-1][j][1])%MOD;
				else{
					dp[i][ch[j].s[k]][0]=(dp[i][ch[j].s[k]][0]+dp[i-1][j][0])%MOD;
					dp[i][ch[j].s[k]][1]=(dp[i][ch[j].s[k]][1]+dp[i-1][j][1])%MOD;
				}
			}
}

signed main(){
	n=in,m=in;
	for(int i=1;i<=n;++i){
		scanf("%s",s+1);
		update();
	}
	get_fail();
	query();
	for(int i=0;i<=sz;++i)
		ans=(ans+dp[m][i][1])%MOD;
	printf("%lld\n",ans);
	return 0;
}

看题看清楚,写数写仔细
模数写错了,D我半小时 😭 😭

T6 数数

fail 树上下放 val数位dp
数位dp注意维护前导 0 和上限

#include<bits/stdc++.h>
#define in Read()
#define int long long
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=3e3+5;
const int MOD=1e9+7;
int m,top,sz;
char n[NNN],s[NNN];
struct Trie{
	int s[10];
	int val,fail;
}ch[NNN];
int f[NNN][NNN];

inline void update(){
	int p=0,len=strlen(s+1);
	for(int i=1;i<=len;++i){
		if(!ch[p].s[s[i]-'0']) ch[p].s[s[i]-'0']=++sz;
		p=ch[p].s[s[i]-'0'];
	}
	ch[p].val=1;
	return;
}

inline void get_fail(){
	int p=0;queue<int>q;
	for(int i=0;i<10;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<10;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
		}
		ch[u].val+=ch[ch[u].fail].val;
	}
}

inline int dp(int pos/*数位*/,int p/*节点*/,int lim/*上限*/,int lead/*前导零*/){
	if(pos>top) return !lead;//考虑前导零
	if(lim&&f[pos][p]!=-1) return f[pos][p];
	int res=0;
	for(int i=0;i<10;++i){
		if(!lim&&i>n[pos]-'0') break;
		if(lead){
			if(!ch[ch[0].s[i]].val)
				res=(res+dp(pos+1,ch[0].s[i],lim||i<n[pos]-'0',lead&&!i))%MOD;
		}
		else{
			if(!ch[ch[p].s[i]].val)
				res=(res+dp(pos+1,ch[p].s[i],lim||i<n[pos]-'0',0))%MOD;
		}
	}
	if(lim&&!lead) f[pos][p]=res;
	return res;
}

signed main(){
	scanf("%s",n+1);
	top=strlen(n+1);
	m=in;
	for(int i=1;i<=m;++i){
		scanf("%s",s+1);
		update();
	}
	get_fail();
	memset(f,-1,sizeof(f));
	printf("%lld\n",dp(1,0,0,1));
	return 0;
}

T7 阿狸的打字机

定义第 x x x个打印的字符串为模式串,第 y y y个打印的字符串为文本串(字符串问题)
可以观察到,模式串末节点被 fail 指了就会有一个贡献
而只有文本串上的、指向模式串末节点的 fail 才有贡献
考虑点亮文本串上的所有节点,跑 fail

众所周知, fail 是棵树
考虑树剖,这玩意 个鬼
DFS序 + 树状数组比它优

  • 首先,打字机是个栈,得到所有字符串,建 trie(显然树状数组着建要优一些,栈不优)
  • 然后,边get_fail 边建 fail树DFS序 编号
  • 接着,对于所有询问,因为离线,可以排序,对于文本串相同的多组数据,可以优地统计模式串
    (已知区间/区间固定,这也就决定了根据单点改,区间和)
  • 最后,单点修改,遇到根节点,区间求和(差分)统计子树,注意不往下找的判据是儿子深度比爸爸低(get_fail 的时候没这个儿子,就跳到 fail 儿子去了),over
#include<bits/stdc++.h>
#define in Read()
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=1e6+10;
char txt[NNN];
struct Trie{
	int s[26];
	int val,fail;
	int dep;//fail树上深度 
}ch[NNN];
int f[NNN];//trie上节点父亲 
int en[NNN],n;//每个字符串的末节点 
int sz;

inline void build(){
	scanf("%s",txt+1);
	int p=0,len=strlen(txt+1),c;
	for(int i=1;i<=len;++i){
		if(txt[i]=='B'){
			p=f[p];
			continue;
		}
		if(txt[i]=='P'){
			ch[p].val=++n;
			en[n]=p;
			continue;
		}
		c=txt[i]-'a';
		if(!ch[p].s[c]) ch[p].s[c]=++sz;
		f[ch[p].s[c]]=p;
		p=ch[p].s[c];
	}
	return;
}

struct Road{
	int nxt,to;
}road[NNN<<1];
int tot_road,first[NNN];

inline void add(int u,int v){
	++tot_road;
	road[tot_road].nxt=first[u];
	first[u]=tot_road;
	road[tot_road].to=v;
}

inline void get_fail(){
	queue<int>q;
	int p=0;
	for(int i=0;i<26;++i){
		p=ch[0].s[i];
		if(!p) continue;
		q.push(p);
		ch[p].fail=0;
		ch[p].dep=ch[0].dep+1;
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;++i){
			p=ch[u].s[i];
			if(!p){
				ch[u].s[i]=ch[ch[u].fail].s[i];
				continue;
			}
			q.push(p);
			int v=ch[u].fail;
			while(v&&!ch[v].s[i]) v=ch[v].fail;
			ch[p].fail=ch[v].s[i];
			ch[p].dep=ch[u].dep+1;
		}
		add(u,ch[u].fail),add(ch[u].fail,u);
	}
}

int dfn[NNN];//DFS序/时间戳 
int siz[NNN];//子树大小 
int tot;//DFS序 

inline void Dfs(int fa,int u){
	int v;
	dfn[u]=++tot;
	siz[u]=1;
	for(int e=first[u];e;e=road[e].nxt){
		v=road[e].to;
		if(!(v^fa)) continue;
		Dfs(u,v);
		siz[u]+=siz[v];
	}
}

int m;
struct Query{
	int x,y,id,ans;
}qur[NNN];
int where[NNN];//记录排序后每种文本串的第一个询问 

inline bool cmp1(const Query &u,const Query &v){return u.y<v.y;}
inline bool cmp2(const Query &u,const Query &v){return u.id<v.id;}

inline void get_query(){
	m=in;
	for(int i=1;i<=m;++i){
		qur[i].x=in,qur[i].y=in;
		qur[i].id=i;
	}
	sort(qur+1,qur+m+1,cmp1);
	for(int i=1;i<=m;++i)
		if(qur[i].y^qur[i-1].y)
			where[qur[i].y]=i;
}

int tree[NNN];
inline int lowbit(int x){return x&(-x);}

inline void update(int p,int w){
	while(p<=tot){
		tree[p]+=w;
		p+=lowbit(p);
	}
}

inline int query(int p){
	int res=0;
	while(p){
		res+=tree[p];
		p-=lowbit(p);
	}
	return res;
}

inline void work(int id){
	if(!where[id]) return;
	int i=where[id],p;
	while(true){
		p=en[qur[i].x];
		qur[i].ans=query(dfn[p]+siz[p]-1)-query(dfn[p]-1);
		if(qur[i+1].y^qur[i].y) break;
		++i;
	}
}

inline void search(int u){
	update(dfn[u],1);
	if(ch[u].val) work(ch[u].val);
	int p;
	for(int i=0;i<26;++i){
		p=ch[u].s[i];
		if(!p) continue;
		if(ch[p].dep<=ch[u].dep) continue;
		search(p);
	}
	update(dfn[u],-1);
}

inline void print(){
	sort(qur+1,qur+m+1,cmp2);
	for(int i=1;i<=m;++i)
		printf("%d\n",qur[i].ans);
}

int main(){
	build();
	get_fail();
	Dfs(0,0);
	get_query();
	search(0);
	print();
	return 0;
}

T8 strings

在这里插入图片描述
在这里插入图片描述
考试题,当时不会,现在会了
ACMDP,稍微改了一下 val 的定义
d p i , j dp_{i,j} dpi,j表示长度为 i i i,字典树上 j j j节点的最优方案
考虑转移方程:
d p i , s o n p = max ⁡ { d p i − 1 , p + v a l s o n p } dp_{i,son_p}=\max\{dp_{i-1,p}+val_{son_p}\} dpi,sonp=max{dpi1,p+valsonp}

#include<bits/stdc++.h>
#define in Read()
#define int long long
using namespace std;
inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')ch=getchar(),f=-1;
	while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=1e3+10;
const int INF=1e9+10;
int n,L,a[NNN],sz;
char s[NNN];
int ch[NNN][26],fail[NNN],val[NNN];
int dp[NNN][NNN],ans;
int same[NNN],idx[NNN];

inline void build(int id){
	int p=0,len=strlen(s+1);
	for(int i=1;i<=len;++i){
		if(!ch[p][s[i]-'a']) ch[p][s[i]-'a']=++sz;
		p=ch[p][s[i]-'a'];
	}
	if(idx[p]) same[id]=idx[p],val[p]+=a[id];
	else val[p]=a[id],idx[p]=id;
}

inline void get_fail(){
	queue<int>q;int p=0;
	for(int i=0;i<26;++i){
		p=ch[0][i];
		if(!p) continue;
		q.push(p);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<26;++i){
			p=ch[u][i];
			if(!p){
				ch[u][i]=ch[fail[u]][i];
				continue;
			}
			q.push(p);
			int v=fail[u];
			while(v&&!ch[v][i]) v=fail[v];
			fail[p]=ch[v][i];
		}
		val[u]+=val[fail[u]];
	}
}

inline void query(){
	for(int i=0;i<=L;++i)
		for(int j=0;j<=sz;++j)
			dp[i][j]=-INF;
	dp[0][0]=0;
	for(int i=1;i<=L;++i)
		for(int j=0;j<=sz;++j)
			for(int c=0;c<26;++c)
				dp[i][ch[j][c]]=max(dp[i][ch[j][c]],dp[i-1][j]+val[ch[j][c]]);
}

signed main(){
	n=in,L=in;
	for(int i=1;i<=n;++i) a[i]=in;
	for(int i=1;i<=n;++i){
		scanf("%s",s+1);
		build(i);
	}
	get_fail();
	query();
	for(int i=0;i<=sz;++i){
		if(!same[i]) ans=max(ans,dp[L][i]);
		else ans=max(ans,dp[L][same[i]]);
	}
	printf("%lld\n",ans);
	return 0;
}

T8 e-Government

类似 T7,且显然暴力 T
但是不像 T7,在线,没有排序的优化了, O ( n log ⁡ n ) O(n\log n) O(nlogn)
无法排序也就决定区间不定,因此只能单点改,区间和

首先,建 trie,边 get_fail 边建树
然后,DFS排序,区间加上传现有单词集
最后,对于每个操作,区间加/单点改

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while (!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while (isdigit(ch))
		i=(i<<1)+(i<<3)+ch-48, ch=getchar();
	return i*f;
}

const int N=1e6+5;
int n,m,sz;
char s[N];
bool vis[N];
int tr[N][26],pos[N],idx[N];
int fail[N];
int tot,first[N],nxt[N<<1],aim[N<<1];
int dfn[N],siz[N],ord;
int tre[N];

void ljb(int u,int v){
	++tot;
	nxt[tot]=first[u];
	first[u]=tot;
	aim[tot]=v;
	return;
}

void build(int id){
	int len=strlen(s),p=0;
	for(int i=0;i<len;++i){
		int c=s[i]-'a';
		if(!tr[p][c]) tr[p][c]=++sz;
		p=tr[p][c];
	}
	pos[id]=p;
	idx[p]=id;
	return;
}

void get_fail(){
	queue<int> q;
	int p=0;
	for(int i=0;i<26;++i){
		p=tr[0][i];
		if(!p) continue;
		q.push(p);
		fail[p]=0;
	}
	
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int c=0;c<26;++c){
			p=tr[u][c];
			if(!p){
				tr[u][c]=tr[fail[u]][c];
				continue;
			}
			q.push(p);
			int v=fail[u];
			while(v&&!tr[v][c]) v=fail[v];
			fail[p]=tr[v][c];
		}
		ljb(u,fail[u]);
		ljb(fail[u],u);
	}
	return;
}

void DFS(int u,int fa){
//	printf("%d %d\n",u,fa);
	dfn[u]=++ord;
	siz[u]=1;
	for(int e=first[u];e;e=nxt[e]){
		int v=aim[e];
		if(v==fa) continue;
		DFS(v,u);
		siz[u]+=siz[v];
	}
	return;
}

int lowbit(int x){
	return x&(-x);
}

void add(int p,int v){
//	cout<<p<<endl;
	while(p<=ord){
		tre[p]+=v;
		p+=lowbit(p);
	}
	return;
}

int query(int p){
	int res=0;
	while(p){
		res+=tre[p];
		p-=lowbit(p);
	}
	return res;
}

int find(){
	int p=0;
	int ans=0;
	int len=strlen(s);
	for(int i=0;i<len;++i){
		int c=s[i]-'a';
		p=tr[p][c];
		ans+=query(dfn[p]);
	}
	return ans;
}

int main(){
	n=in,m=in;
	for(int i=1;i<=m;++i){
		scanf("%s",s);
		build(i);
	}
	
	get_fail();
	DFS(0,0);
	
	for(int i=1;i<=m;++i){
//		cout<<pos[i]<<"=="<<endl;
		vis[i]=true;
		int p=pos[i];
		add(dfn[p],1);
		add(dfn[p]+siz[p],-1);
	}
	
	for(int i=1;i<=n;++i){
		char op=getchar();
		while(op!='+'&&op!='-'&&op!='?')
			op=getchar();
		if(op=='+'){
			int x=in;
			if(vis[x]) continue;
			vis[x]=true;
			x=pos[x];
			add(dfn[x],1);
			add(dfn[x]+siz[x],-1);
		}else if(op=='-'){
			int x=in;
			if(!vis[x]) continue;
			vis[x]=false;
			x=pos[x];
			add(dfn[x],-1);
			add(dfn[x]+siz[x],1);
		}else if(op=='?'){
			scanf("%s",s);
			printf("%d\n",find());
		}
	}
	
	return 0;
}

T9 Indie Album

都说这是多倍经验怎么我觉得它们都挺不同的呢

离线下来做即可
trie树上深搜,如果遇到要求的点就统计它的贡献

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while (!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while (isdigit(ch))
		i=(i<<1)+(i<<3)+ch-48, ch=getchar();
	return i*f;
}

const int N=1e6+5;
int n,m,sz;
int tr[N][26];
int val[N],sam[N],pos[N],sm[N];
int fail[N];
char s[N];
int ww[N];
int dep[N];
int tot,first[N],nxt[N<<1],aim[N<<1];
int ord,dfn[N],siz[N];
int tre[N];

struct Query{
	int x,y,id,ans;
}qur[N];

bool cmp1(const Query &u,const Query &v){
	return u.y<v.y;
}

bool cmp2(const Query &u,const Query &v){
	return u.id<v.id;
}

void ljb(int u,int v){
	++tot;
	nxt[tot]=first[u];
	first[u]=tot;
	aim[tot]=v;
	return;
}

int lowbit(int x){
	return x&(-x);
}

void add(int p,int v){
	while(p<=ord){
		tre[p]+=v;
		p+=lowbit(p);
	}
	return;
}

int query(int p){
	int res=0;
	while(p){
		res+=tre[p];
		p-=lowbit(p);
	}
	return res;
}

void insert(int p,int c,int id){
	if(!tr[p][c])
		tr[p][c]=++sz;
	p=tr[p][c];
	if(val[p]) sm[id]=val[p];
	else val[p]=id;
	pos[id]=p;
	return;
}

int build(int id){
	int p=0;
	int len=strlen(s);
	for(int i=0;i<len;++i){
		int c=s[i]-'a';
		if(!tr[p][c])
			tr[p][c]=++sz;
		p=tr[p][c];
	}
	return p;
}

void scan(){
	n=in;
	for(int i=1;i<=n;++i){
		int op=in;
		if(op==1){
			char c=getchar();
			while(!isalpha(c))
				c=getchar();
			insert(0,c-'a',i);
		}else{
			int p=in;
			char c=getchar();
			while(!isalpha(c))
				c=getchar();
			insert(pos[p],c-'a',i);
		}
	}
	
	m=in;
	for(int i=1;i<=m;++i){
		qur[i].y=in,qur[i].id=i;
		if(sm[qur[i].y])
			qur[i].y=sm[qur[i].y];
		scanf("%s",s);
		qur[i].x=build(i);
	}
	
	sort(qur+1,qur+m+1,cmp1);
	for(int i=1;i<=m;++i){
		if(qur[i].y==qur[i-1].y) continue;
		ww[qur[i].y]=i;
	}
	
	return;
}

void get_fail(){
	queue<int> q;
	int p=0;
	for(int i=0;i<26;++i){
		p=tr[0][i];
		if(!p) continue;
		q.push(p);
		fail[p]=0;
		dep[p]=dep[0]+1;
	}
	
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int c=0;c<26;++c){
			p=tr[u][c];
			if(!p){
				tr[u][c]=tr[fail[u]][c];
				continue;
			}
			q.push(p);
			int v=fail[u];
			while(v&&!tr[v][c]) v=fail[v];
			fail[p]=tr[v][c];
			dep[p]=dep[u]+1;
		}
		ljb(u,fail[u]);
		ljb(fail[u],u);
	}
	
	return;
}

void DFS(int u,int fa){
	dfn[u]=++ord;
	siz[u]=1;
	for(int e=first[u];e;e=nxt[e]){
		int v=aim[e];
		if(v==fa) continue;
		DFS(v,u);
		siz[u]+=siz[v];
	}
	return;
}

void work(int id){
	int i=ww[id];
	while(qur[i].y==id){
		int u=qur[i].x;
		qur[i].ans=
			query(dfn[u]+siz[u]-1)-
			query(dfn[u]-1);
		++i;
	}
	return;
}

void search(int u){
	add(dfn[u],1);
	if(val[u]) work(val[u]);
	for(int c=0;c<26;++c){
		int p=tr[u][c];
		if(dep[p]<=dep[u]) continue;
		search(p);
	}
	add(dfn[u],-1);
	return;
}

void print(){
	sort(qur+1,qur+m+1,cmp2);
	for(int i=1;i<=m;++i)
		printf("%d\n",qur[i].ans);
	return;
}

int main(){
	scan();
	get_fail();
	DFS(0,0);
	search(0);
	print();
	return 0;
}

T10 [POI2000]病毒

手玩发现在 ACM 上存在没有标记的环即有无限长的安全代码,找环即可

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while (!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while (isdigit(ch))
		i=(i<<1)+(i<<3)+ch-48, ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,sz;
char s[N];
int tr[N][2],fail[N];
bool val[N],vis[N],tag[N];

void build(){
	int p=0;
	int len=strlen(s);
	for(int i=0;i<len;++i){
		int c=s[i]-'0';
		if(!tr[p][c])
			tr[p][c]=++sz;
		p=tr[p][c];
	}
	val[p]=true;
	return;
}

void get_fail(){
	queue<int> q;
	int p=0;
	for(int c=0;c<2;++c){
		p=tr[0][c];
		if(!p) continue;
		q.push(p);
		fail[p]=0;
	}
	
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int c=0;c<2;++c){
			int p=tr[u][c];
			if(!p){
				tr[u][c]=tr[fail[u]][c];
				continue;
			}
			q.push(p);
			int v=fail[u];
			while(v&&!tr[v][c]) v=fail[v];
			fail[p]=tr[v][c];
		}
		val[u]+=val[fail[u]];
	}
	
	return;
}

void work(int p){
	if(tag[p]){
		puts("TAK");
		exit(0);
	}
	
	if(val[p]||vis[p]) return;
	tag[p]=vis[p]=true;
	for(int c=0;c<2;++c){
		if(tr[p][c])
			work(tr[p][c]);
	}
	tag[p]=false;
	return;
		
}

int main(){
	n=in;
	for(int i=1;i<=n;++i){
		scanf("%s",s);
		build();
	}
	
	if(!tr[0][0]||!tr[0][1]){
		puts("TAK");
		return 0;
	}
	
	get_fail();
	work(0);
	puts("NIE");
	return 0;
}

T11 You Are Given Some Strings…

在这里插入图片描述
正做一边,反做一遍
枚举拼接点,乘起来即可

#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define int long long
int in{
	int i=0,f=1;char ch=0;
	while (!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while (isdigit(ch))
		i=(i<<1)+(i<<3)+ch-48, ch=getchar();
	return i*f;
}

const int N=3e5+5;
int n,res;

struct ACM{
	
	int sz;
	int val[N];
	int ans[N];
	int fail[N];
	int last[N];
	int tr[N][26];
	char txt[N],s[N];
	
	void build(){
		int p=0;
		int len=strlen(s);
		for(int i=0;i<len;++i){
			int c=s[i]-'a';
			if(!tr[p][c])
				tr[p][c]=++sz;
			p=tr[p][c];
		}
		++val[p];
	}
	
	void get_fail(){
		queue<int> q;
		int p=0;
		for(int c=0;c<26;++c){
			p=tr[0][c];
			if(!p) continue;
			q.push(p);
			fail[p]=0;
		}
		
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int c=0;c<26;++c){
				p=tr[u][c];
				if(!p){
					tr[u][c]=tr[fail[u]][c];
					continue;
				}
				q.push(p);
				int v=fail[u];
				while(v&&!tr[v][c]) v=fail[v];
				fail[p]=tr[v][c];
				last[p]=val[fail[p]]?fail[p]:last[fail[p]];
			}
		}
		
		return;
	}
	
	void find(){
		int p=0;
		int len=strlen(txt);
		for(int i=0;i<len;++i){
			int c=txt[i]-'a';
			p=tr[p][c];
			int res=0;
			int v=val[p]?p:last[p];
			while(v){
				res+=val[v];
				v=last[v];
			}
			ans[i]=res;
		}
		return;
	}
	
}acm1,acm2;

void get_ans(){
	res=0;
	int len=strlen(acm1.txt);
	for(int i=0;i<len;++i)
		res+=acm1.ans[i]*acm2.ans[len-i-2];
	return;
}

signed main(){
	scanf("%s",acm1.txt);
	memcpy(acm2.txt,acm1.txt,sizeof acm2.txt);
	reverse(acm2.txt,acm2.txt+strlen(acm2.txt));
	
	n=in;
	for(int i=1;i<=n;++i){
		scanf("%s",acm1.s);
		acm1.build();
		memcpy(acm2.s,acm1.s,sizeof acm2.s);
		reverse(acm2.s,acm2.s+strlen(acm2.s));
		acm2.build();
	}
	
	acm1.get_fail();
	acm2.get_fail();
	acm1.find();
	acm2.find();
	
	get_ans();
	printf("%lld\n",res);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值