ACAM 题乱做
本篇笔记尚待修订
之前做了不少 ACAM,不过没怎么整理起来,还是有点可惜的。
打 * 的是推荐一做的题目。
I. *CF1437G Death DBMS
见 我的题解。
II. *CF1202E You Are Given Some Strings...
见 我的题解。
III. *CF1400F x-prime Substrings
题意简述:一个字符串为 x-prime 当且仅当它每一位数字之和为
且其所有子串的每一位数字之和不为 的真约数(即 的不为 的约数绕)。求给出字符串 至少要删掉多少字符才能使其不包含 x-prime 的子字符串。
hot tea.
一个并不显然的条件是对于所有
记
/*
Powered by C++11.
Author : Alex_Wei.
*/
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(3)
//#define int long long
#define pb emplace_back
#define mem(x,v) memset(x,v,sizeof(x))
const int S=4e4+5;
const int N=1e3+5;
int n,x,cnt,ans=S,son[S][10],f[S],ed[S],g[N][S];
string s;
void ins(string s){
int p=0;
for(char it:s){
if(!son[p][it-'0'])son[p][it-'0']=++cnt;
p=son[p][it-'0'];
} ed[p]=1;
} void build(){
queue <int> q;
for(int i=0;i<10;i++)if(son[0][i])q.push(son[0][i]);
while(!q.empty()){
int t=q.front(); q.pop();
for(int i=0;i<10;i++)
if(son[t][i])q.push(son[t][i]),f[son[t][i]]=son[f[t]][i];
else son[t][i]=son[f[t]][i];
ed[t]|=ed[f[t]];
}
} bool check(string s){
for(int i=0;i<s.size();i++)
for(int j=i;j<s.size();j++){
int cnt=0;
for(int k=i;k<=j;k++)cnt+=s[k]-'0';
if(cnt<x&&x%cnt==0)return 0;
} return 1;
} void dfs(int num,string s=""){
if(num==x){
if(check(s))ins(s);
return;
} for(int i=1;i<10;i++)
if(num+i<=x)
dfs(num+i,s+(char)(i+'0'));
}
int main(){
cin>>s>>x,dfs(0),build();
mem(g,0x3f),g[0][0]=0;
for(int i=0;i<s.size();i++)
for(int j=0;j<=cnt;j++){
int p=son[j][s[i]-'0'];
if(!ed[p])g[i+1][p]=min(g[i+1][p],g[i][j]);
g[i+1][j]=min(g[i+1][j],g[i][j]+1);
}
for(int i=0;i<=cnt;i++)ans=min(ans,g[s.size()][i]);
cout<<ans<<endl;
return 0;
}
IV. *CF1207G Indie Album
题意简述:有
种操作,给出整数,整数和字符 。若 则 ;否则 。 次询问给出 ,求 在 中的出现次数。
以前打过这场比赛,要是我当时会 ACAM 多好啊。
注意到如果我们对操作串
可是
时间复杂度
/*
Powered by C++11.
Author : Alex_Wei.
*/
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(3)
//#define int long long
#define pb emplace_back
const int N=4e5+5;
int n,m,ans[N];
int cnt,dn,son[N][26],ed[N],fa[N],sz[N],dfn[N];
vector <int> e[N],f[N],ft[N];
char ad[N];
void ins(int id,string s){
int p=0;
for(char it:s){
if(!son[p][it-'a'])son[p][it-'a']=++cnt;
p=son[p][it-'a'];
} ed[id]=p;
} void build(){
queue <int> q;
for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
while(!q.empty()){
int t=q.front(); q.pop();
for(int i=0;i<26;i++)
if(son[t][i])q.push(son[t][i]),fa[son[t][i]]=son[fa[t]][i];
else son[t][i]=son[fa[t]][i];
ft[fa[t]].pb(t);
}
} void dfs(int id){
dfn[id]=++dn,sz[id]=1;
for(int it:ft[id])dfs(it),sz[id]+=sz[it];
}
int c[N];
void add(int x,int v){while(x<=dn)c[x]+=v,x+=x&-x;}
int query(int x){int ans=0; while(x)ans+=c[x],x-=x&-x; return ans;}
int query(int l,int r){return query(r)-query(l-1);}
void cal(int id,int p){
if(id)p=son[p][ad[id]-'a'],add(dfn[p],1);
for(int it:e[id])ans[it]=query(dfn[ed[it]],dfn[ed[it]]+sz[ed[it]]-1);
for(int it:f[id])cal(it,p);
add(dfn[p],-1);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int tp,p=0; cin>>tp;
if(tp==2)cin>>p;
f[p].pb(i),cin>>ad[i];
} cin>>m;
string q;
for(int i=1,id;i<=m;i++)
cin>>id>>q,e[id].pb(i),ins(i,q);
build(),dfs(0),cal(0,0);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
V. *P4569 [BJWC2011]禁忌
见 我的题解。
VI. *CF1483F Exam
题意简述:给出字典
,求有多少对 满足 , 且不存在 使得 且 。
hot tea!赛时看 F 的时候只剩 40min 了,估摸着写不出来就没写。事实上,这是一个巨大的错误。
对于这种字符串匹配的题目优先考虑 ACAM & SAM,不过这里 SAM 似乎不太好做(因为要广义 SAM,实际上也是可以的),故选用 ACAM。
考虑枚举每一个串
最长的
为什么要倒序枚举
还有这个求出现次数是 ACAM 基操了,dfs 序 + 树状数组维护一下即可。
时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int S=26;
int node,son[N][S],fa[N],ed[N],edp[N];
int n,ans,dnum,dfn[N],sz[N];
string s[N];
vector <int> e[N];
void ins(string s,int id){
int p=0;
for(char it:s){
if(!son[p][it-'a'])son[p][it-'a']=++node;
p=son[p][it-'a'];
} ed[p]=id,edp[id]=p;
} void build(){
queue <int> q;
for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
while(!q.empty()){
int t=q.front(); q.pop();
for(int i=0;i<26;i++)
if(son[t][i])q.push(son[t][i]),fa[son[t][i]]=son[fa[t]][i];
else son[t][i]=son[fa[t]][i];
ed[t]=ed[t]?ed[t]:ed[fa[t]];
e[fa[t]].push_back(t);
}
} void dfs(int id){
dfn[id]=++dnum,sz[id]=1;
for(int it:e[id])dfs(it),sz[id]+=sz[it];
}
int c[N],buc[N];
void add(int x,int v){while(x<=dnum)c[x]+=v,x+=x&-x;}
int query(int x){int ans=0; while(x)ans+=c[x],x-=x&-x; return ans;}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>s[i],ins(s[i],i);
build(),dfs(0);
for(int i=1;i<=n;i++){
vector <int> pa,cnt;
int p=0,pre=1e9;
for(char it:s[i]){
pa.push_back(p=son[p][it-'a']);
add(dfn[p],1);
} for(int j=pa.size()-1;~j;j--){
int id=j==pa.size()-1?ed[fa[pa[j]]]:ed[pa[j]];
if(!id)continue;
int l=j-s[id].size();
if(l<pre)pre=l,buc[id]++,cnt.push_back(id);
} for(int it:cnt){
if(!buc[it])continue;
int p=edp[it],ap=query(dfn[p]+sz[p]-1)-query(dfn[p]-1);
if(ap==buc[it])ans++; buc[it]=0;
} for(int p:pa)add(dfn[p],-1);
} cout<<ans<<endl;
return 0;
}
VII. CF163E e-Government
好久没写 ACAM,都快忘掉了。
显然,对于这类字符串匹配问题,我们最好的选择是 SAM ACAM。当然这题应该也可以用广义 SAM 来做,就是把所有询问的字符串和原来的字符串全部拿过来搞一个广义 SAM,修改就类似 ACAM 用 fail 树的 dfs 序 + BIT 维护一下即可。
一不小心直接讲完了。
首先对字符串集合
但是,因为链的顶端是根节点,所以有一个经典的单点修改 + 链和 转 子树修改 + 单点查询的经典套路:对于每次单点修改,将其影响扩大至该点的整个子树,那么每次链和查询只需要求链底这一点的值即可。显然,后者可以 dfs 序 + BIT 轻松维护。时间复杂度
两个注意点:
- 多次重复添加算一次,删除也是。
- BIT 循环上界不是
而是 ACAM 节点个数。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,k,buc[N];
int node,ed[N],son[N][26],fa[N];
vector <int> e[N];
void ins(string s,int id){
int p=0;
for(char it:s){
if(!son[p][it-'a'])son[p][it-'a']=++node;
p=son[p][it-'a'];
} ed[id]=p;
}
void build(){
queue <int> q;
for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
while(!q.empty()){
int t=q.front(); q.pop();
for(int i=0;i<26;i++)
if(son[t][i])fa[son[t][i]]=son[fa[t]][i],q.push(son[t][i]);
else son[t][i]=son[fa[t]][i];
e[fa[t]].push_back(t);
}
}
int dnum,dfn[N],sz[N],c[N];
void add(int x,int v){while(x<=node)c[x]+=v,x+=x&-x;}
int query(int x){int s=0; while(x)s+=c[x],x-=x&-x; return s;}
void dfs(int id){
dfn[id]=dnum++,sz[id]=1;
for(int it:e[id])dfs(it),sz[id]+=sz[it];
}
int main(){
cin>>n>>k;
for(int i=1;i<=k;i++){
string s; cin>>s,ins(s,i);
}
build(),dfs(0);
for(int i=1;i<=k;i++){
int id=ed[i];
add(dfn[id],1);
add(dfn[id]+sz[id],-1);
buc[i]=1;
}
for(int i=1;i<=n;i++){
char c; cin>>c;
if(c=='?'){
string s; cin>>s;
long long p=0,ans=0;
for(char it:s){
p=son[p][it-'a'];
ans+=query(dfn[p]);
}
cout<<ans<<endl;
}
else if(c=='-'){
int id; cin>>id;
if(!buc[id])continue;
buc[id]=0;
id=ed[id];
add(dfn[id],-1);
add(dfn[id]+sz[id],1);
}
else if(c=='+'){
int id; cin>>id;
if(buc[id])continue;
buc[id]=1;
id=ed[id];
add(dfn[id],1);
add(dfn[id]+sz[id],-1);
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现