之前做了不少 ACAM,不过没怎么整理起来,还是有点可惜的。

打 * 的是推荐一做的题目。

I. *CF1437G Death DBMS

我的题解

II. *CF1202E You Are Given Some Strings...

我的题解

III. *CF1400F x-prime Substrings

题意简述:一个字符串为 x-prime 当且仅当它每一位数字之和为 \(x\) 且其所有子串的每一位数字之和不为 \(x\) 的真约数(即 \(x\) 的不为 \(x\) 的约数绕)。求给出字符串 \(s\) 至少要删掉多少字符才能使其不包含 x-prime 的子字符串。

在洛谷博客查看

hot tea.

一个并不显然的条件是对于所有 \(x\),x-prime 字符串的总长度不超过 \(6000\)。可能的原因是字符串中不能含有 \(\texttt{1}\)(除了 \(x=1\))。那么暴力 dfs 就可以找到所有字符串,对其建立一个 ACAM,然后在上面 DP 即可。设 \(f_{i,p}\) 表示 \(s[1:i]\) 至少删掉多少字符才能在 ACAM 上跑到状态 \(p\)。记 \(nxt=son_{p,s_{i+1}}\),若 \(nxt\) 在 fail 树上与根节点的链之间没有终止节点(这是基本操作),那么可以更新 \(f_{i+1,nxt}\gets \min(f_{i+1,nxt},f_{i,p})\)。同时别忘记更新 \(f_{i+1,j}\gets \min(f_{i+1,j},f_{i,j}+1)\),表示删掉 \(s_{i+1}\)。

记 \(L_x\) 为所有 x-prime 字符串的长度之和,\(\Sigma\) 为字符集,则时间复杂度为 \(\mathcal{O}(nL_x|\Sigma|)\),空间可以通过滚动数组优化(不过没有必要),可以通过。

  1. /*
  2. Powered by C++11.
  3. Author : Alex_Wei.
  4. */
  5. #include <bits/stdc++.h>
  6. using namespace std;
  7. //#pragma GCC optimize(3)
  8. //#define int long long
  9. #define pb emplace_back
  10. #define mem(x,v) memset(x,v,sizeof(x))
  11. const int S=4e4+5;
  12. const int N=1e3+5;
  13. int n,x,cnt,ans=S,son[S][10],f[S],ed[S],g[N][S];
  14. string s;
  15. void ins(string s){
  16. int p=0;
  17. for(char it:s){
  18. if(!son[p][it-'0'])son[p][it-'0']=++cnt;
  19. p=son[p][it-'0'];
  20. } ed[p]=1;
  21. } void build(){
  22. queue <int> q;
  23. for(int i=0;i<10;i++)if(son[0][i])q.push(son[0][i]);
  24. while(!q.empty()){
  25. int t=q.front(); q.pop();
  26. for(int i=0;i<10;i++)
  27. if(son[t][i])q.push(son[t][i]),f[son[t][i]]=son[f[t]][i];
  28. else son[t][i]=son[f[t]][i];
  29. ed[t]|=ed[f[t]];
  30. }
  31. } bool check(string s){
  32. for(int i=0;i<s.size();i++)
  33. for(int j=i;j<s.size();j++){
  34. int cnt=0;
  35. for(int k=i;k<=j;k++)cnt+=s[k]-'0';
  36. if(cnt<x&&x%cnt==0)return 0;
  37. } return 1;
  38. } void dfs(int num,string s=""){
  39. if(num==x){
  40. if(check(s))ins(s);
  41. return;
  42. } for(int i=1;i<10;i++)
  43. if(num+i<=x)
  44. dfs(num+i,s+(char)(i+'0'));
  45. }
  46. int main(){
  47. cin>>s>>x,dfs(0),build();
  48. mem(g,0x3f),g[0][0]=0;
  49. for(int i=0;i<s.size();i++)
  50. for(int j=0;j<=cnt;j++){
  51. int p=son[j][s[i]-'0'];
  52. if(!ed[p])g[i+1][p]=min(g[i+1][p],g[i][j]);
  53. g[i+1][j]=min(g[i+1][j],g[i][j]+1);
  54. }
  55. for(int i=0;i<=cnt;i++)ans=min(ans,g[s.size()][i]);
  56. cout<<ans<<endl;
  57. return 0;
  58. }

IV. *CF1207G Indie Album

题意简述:有 \(n\) 种操作,给出整数,整数和字符 \(op,j(op=2),c\)。若 \(op=1\) 则 \(s_i=c\);否则 \(s_i=s_j+c\)。\(m\) 次询问给出 \(i,t\),求 \(t\) 在 \(s_i\) 中的出现次数。

在洛谷博客查看

以前打过这场比赛,要是我当时会 ACAM 多好啊。

注意到如果我们对操作串 \(s\) 建出 ACAM 需要动态修改 fail 树的结构,不太可行。那么换个思路,考虑对所有询问串 \(t\) 建出 ACAM。那么这样就是在 ACAM 上跑 \(s_i\),求出有多少个跑到的节点在 fail 树上以 \(t\) 的终止节点的子树中。这个可以对 fail 树进行一遍 dfs,用每个节点的 dfs 序和 size 维护。这样就是单点修改,区间查询,用树状数组即可。

可是 \(s_i\) 的总长度可能会很大。不难发现每个 \(s_i\) 形成了一个依赖关系,建出树,我们只需要再对这个 “操作树” 进行 dfs,先计算贡献(位置 \(son_{p,c_i}\) 加上 \(1\)),再更新并下传跑到的位置 \(p=son_{p,c_i}\),最后撤销贡献即可。

时间复杂度 \(\mathcal{O}((n+m)\log \sum|t|)\)。

  1. /*
  2. Powered by C++11.
  3. Author : Alex_Wei.
  4. */
  5. #include <bits/stdc++.h>
  6. using namespace std;
  7. //#pragma GCC optimize(3)
  8. //#define int long long
  9. #define pb emplace_back
  10. const int N=4e5+5;
  11. int n,m,ans[N];
  12. int cnt,dn,son[N][26],ed[N],fa[N],sz[N],dfn[N];
  13. vector <int> e[N],f[N],ft[N];
  14. char ad[N];
  15. void ins(int id,string s){
  16. int p=0;
  17. for(char it:s){
  18. if(!son[p][it-'a'])son[p][it-'a']=++cnt;
  19. p=son[p][it-'a'];
  20. } ed[id]=p;
  21. } void build(){
  22. queue <int> q;
  23. for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
  24. while(!q.empty()){
  25. int t=q.front(); q.pop();
  26. for(int i=0;i<26;i++)
  27. if(son[t][i])q.push(son[t][i]),fa[son[t][i]]=son[fa[t]][i];
  28. else son[t][i]=son[fa[t]][i];
  29. ft[fa[t]].pb(t);
  30. }
  31. } void dfs(int id){
  32. dfn[id]=++dn,sz[id]=1;
  33. for(int it:ft[id])dfs(it),sz[id]+=sz[it];
  34. }
  35. int c[N];
  36. void add(int x,int v){while(x<=dn)c[x]+=v,x+=x&-x;}
  37. int query(int x){int ans=0; while(x)ans+=c[x],x-=x&-x; return ans;}
  38. int query(int l,int r){return query(r)-query(l-1);}
  39. void cal(int id,int p){
  40. if(id)p=son[p][ad[id]-'a'],add(dfn[p],1);
  41. for(int it:e[id])ans[it]=query(dfn[ed[it]],dfn[ed[it]]+sz[ed[it]]-1);
  42. for(int it:f[id])cal(it,p);
  43. add(dfn[p],-1);
  44. }
  45. int main(){
  46. cin>>n;
  47. for(int i=1;i<=n;i++){
  48. int tp,p=0; cin>>tp;
  49. if(tp==2)cin>>p;
  50. f[p].pb(i),cin>>ad[i];
  51. } cin>>m;
  52. string q;
  53. for(int i=1,id;i<=m;i++)
  54. cin>>id>>q,e[id].pb(i),ins(i,q);
  55. build(),dfs(0),cal(0,0);
  56. for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
  57. return 0;
  58. }

V. *P4569 [BJWC2011]禁忌

我的题解

VI. *CF1483F Exam

题意简述:给出字典 \(s_i\),求有多少对 \((i,j)\) 满足 \(i\neq j\),\(s_j\in \mathrm{subseq}(s_i)\) 且不存在 \(k\ (k\neq i,k\neq j)\) 使得 \(s_j\in \mathrm{subseq}(s_k)\) 且 \(s_k\in \mathrm{subseq}(s_i)\)。

hot tea!赛时看 F 的时候只剩 40min 了,估摸着写不出来就没写。事实上,这是一个巨大的错误。

对于这种字符串匹配的题目优先考虑 ACAM & SAM,不过这里 SAM 似乎不太好做(因为要广义 SAM,实际上也是可以的),故选用 ACAM。

考虑枚举每一个串 \(s_i\) 作为最长串,那么对于其它的所有串 \(s_k\ (i\neq k)\),\(s_i\) 与 \(s_k\) 符合题意当且仅当 \(s_k\) 在 \(s_i\) 中的出现次数等于 \(s_k\) 在 \(s_i\) 中不被别的串所包含的出现次数。考虑怎么求后者:倒序枚举 \(s_i\) 的每一个位置 \(j\) 作为与别的串 \(s_k\) 匹配的结束位置。找到最长的 \(s_k\) 使得 \(s_k=s_i[j-|s_k|+1:j]\),如果 \([j+1,|s_i|]\) 中所有位置与别的串的成功匹配的左端点的最小值 \(pre\) 大于 \(j-|s_k|+1\),那么这就是 \(s_k\) 的一次不被别的串所包含的出现。维护 \(pre\) 直接用 \(j-|s_k|+1\) 更新即可。

最长的 \(s_k\) 也就是 \(s_i[1:j]\) 在 ACAM 上的状态在 fail 树上最近的结束位置所代表的字符串,在建 ACAM 的时候一并求出即可。别忘了特判一下 \(s_i[1:|s_i|]\),这时就是用该状态的父亲计算上述过程。

为什么要倒序枚举 \(j\):这样后考虑的字符串对一开始考虑的字符串没有影响,因为结束位置在 \(s_i\) 较前的字符串不可能包含结束位置在 \(s_i\) 较后的字符串。而如果正序枚举,那么一开始认为没有被覆盖的字符串很有可能在后面被覆盖了。即若 \(r_1<r_2\),则 \([l_1,r_1]\) 是永远不会覆盖 \([l_2,r_2]\) 的,而 \([l_2,r_2]\) 很有可能覆盖 \([l_1,r_1]\)。这样需要撤销贡献,很麻烦。

还有这个求出现次数是 ACAM 基操了,dfs 序 + 树状数组维护一下即可。

时间复杂度 \(\mathcal{O}(n\log n)\)。这份代码在 CF 上暂时是最短代码(2021.3.23)。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e6+5;
  4. const int S=26;
  5. int node,son[N][S],fa[N],ed[N],edp[N];
  6. int n,ans,dnum,dfn[N],sz[N];
  7. string s[N];
  8. vector <int> e[N];
  9. void ins(string s,int id){
  10. int p=0;
  11. for(char it:s){
  12. if(!son[p][it-'a'])son[p][it-'a']=++node;
  13. p=son[p][it-'a'];
  14. } ed[p]=id,edp[id]=p;
  15. } void build(){
  16. queue <int> q;
  17. for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
  18. while(!q.empty()){
  19. int t=q.front(); q.pop();
  20. for(int i=0;i<26;i++)
  21. if(son[t][i])q.push(son[t][i]),fa[son[t][i]]=son[fa[t]][i];
  22. else son[t][i]=son[fa[t]][i];
  23. ed[t]=ed[t]?ed[t]:ed[fa[t]];
  24. e[fa[t]].push_back(t);
  25. }
  26. } void dfs(int id){
  27. dfn[id]=++dnum,sz[id]=1;
  28. for(int it:e[id])dfs(it),sz[id]+=sz[it];
  29. }
  30. int c[N],buc[N];
  31. void add(int x,int v){while(x<=dnum)c[x]+=v,x+=x&-x;}
  32. int query(int x){int ans=0; while(x)ans+=c[x],x-=x&-x; return ans;}
  33. int main(){
  34. cin>>n;
  35. for(int i=1;i<=n;i++)cin>>s[i],ins(s[i],i);
  36. build(),dfs(0);
  37. for(int i=1;i<=n;i++){
  38. vector <int> pa,cnt;
  39. int p=0,pre=1e9;
  40. for(char it:s[i]){
  41. pa.push_back(p=son[p][it-'a']);
  42. add(dfn[p],1);
  43. } for(int j=pa.size()-1;~j;j--){
  44. int id=j==pa.size()-1?ed[fa[pa[j]]]:ed[pa[j]];
  45. if(!id)continue;
  46. int l=j-s[id].size();
  47. if(l<pre)pre=l,buc[id]++,cnt.push_back(id);
  48. } for(int it:cnt){
  49. if(!buc[it])continue;
  50. int p=edp[it],ap=query(dfn[p]+sz[p]-1)-query(dfn[p]-1);
  51. if(ap==buc[it])ans++; buc[it]=0;
  52. } for(int p:pa)add(dfn[p],-1);
  53. } cout<<ans<<endl;
  54. return 0;
  55. }

VII. CF163E e-Government

好久没写 ACAM,都快忘掉了。

显然,对于这类字符串匹配问题,我们最好的选择是 SAM ACAM。当然这题应该也可以用广义 SAM 来做,就是把所有询问的字符串和原来的字符串全部拿过来搞一个广义 SAM,修改就类似 ACAM 用 fail 树的 dfs 序 + BIT 维护一下即可。

一不小心直接讲完了。

首先对字符串集合 \(S\) 建出 ACAM \(T_S\)。考虑用查询的字符串 \(t\) 在 \(T_S\) 上面跳。根据 ACAM 的实际意义,假设当前通过字符 \(t_i\) 跳到了节点 \(p\),那么在 fail 树上从 \(p\) 到根节点这一整条路径上的所有节点都表示以 \(t_i\) 结尾且与 \(t_{1\sim i}\) 的后缀匹配的 \(S\) 的所有前缀的全新的一次出现。对于 \(S\) 的每个字符串记录它在 \(T_s\) 的末节点,这样就是单点修改 + 链和,可以用树链剖分维护。

但是,因为链的顶端是根节点,所以有一个经典的单点修改 + 链和 转 子树修改 + 单点查询的经典套路:对于每次单点修改,将其影响扩大至该点的整个子树,那么每次链和查询只需要求链底这一点的值即可。显然,后者可以 dfs 序 + BIT 轻松维护。时间复杂度 \(\mathcal{O}(m\log m)\),其中 \(m\) 是字符集大小。

两个注意点:

  • 多次重复添加算一次,删除也是。
  • BIT 循环上界不是 \(n\) 而是 ACAM 节点个数。
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e6+5;
  4. int n,k,buc[N];
  5. int node,ed[N],son[N][26],fa[N];
  6. vector <int> e[N];
  7. void ins(string s,int id){
  8. int p=0;
  9. for(char it:s){
  10. if(!son[p][it-'a'])son[p][it-'a']=++node;
  11. p=son[p][it-'a'];
  12. } ed[id]=p;
  13. }
  14. void build(){
  15. queue <int> q;
  16. for(int i=0;i<26;i++)if(son[0][i])q.push(son[0][i]);
  17. while(!q.empty()){
  18. int t=q.front(); q.pop();
  19. for(int i=0;i<26;i++)
  20. if(son[t][i])fa[son[t][i]]=son[fa[t]][i],q.push(son[t][i]);
  21. else son[t][i]=son[fa[t]][i];
  22. e[fa[t]].push_back(t);
  23. }
  24. }
  25. int dnum,dfn[N],sz[N],c[N];
  26. void add(int x,int v){while(x<=node)c[x]+=v,x+=x&-x;}
  27. int query(int x){int s=0; while(x)s+=c[x],x-=x&-x; return s;}
  28. void dfs(int id){
  29. dfn[id]=dnum++,sz[id]=1;
  30. for(int it:e[id])dfs(it),sz[id]+=sz[it];
  31. }
  32. int main(){
  33. cin>>n>>k;
  34. for(int i=1;i<=k;i++){
  35. string s; cin>>s,ins(s,i);
  36. }
  37. build(),dfs(0);
  38. for(int i=1;i<=k;i++){
  39. int id=ed[i];
  40. add(dfn[id],1);
  41. add(dfn[id]+sz[id],-1);
  42. buc[i]=1;
  43. }
  44. for(int i=1;i<=n;i++){
  45. char c; cin>>c;
  46. if(c=='?'){
  47. string s; cin>>s;
  48. long long p=0,ans=0;
  49. for(char it:s){
  50. p=son[p][it-'a'];
  51. ans+=query(dfn[p]);
  52. }
  53. cout<<ans<<endl;
  54. }
  55. else if(c=='-'){
  56. int id; cin>>id;
  57. if(!buc[id])continue;
  58. buc[id]=0;
  59. id=ed[id];
  60. add(dfn[id],-1);
  61. add(dfn[id]+sz[id],1);
  62. }
  63. else if(c=='+'){
  64. int id; cin>>id;
  65. if(buc[id])continue;
  66. buc[id]=1;
  67. id=ed[id];
  68. add(dfn[id],1);
  69. add(dfn[id]+sz[id],-1);
  70. }
  71. }
  72. return 0;
  73. }

ACAM 题乱做的更多相关文章

  1. 历年NOIP水题泛做

    快noip了就乱做一下历年的noip题目咯.. noip2014 飞扬的小鸟 其实这道题并不是很难,但是就有点难搞 听说男神错了一个小时.. 就是$f_{i,j}$表示在第$i$个位置高度为$j$的时 ...

  2. (各个公司面试原题)在线做了一套CC++综合測试题,也来測一下你的水平吧(二)

    刚才把最后的10道题又看了下.也发上来吧. 以下给出试题.和我对题目的一些理解 前10道题地址 (各个公司面试原题)在线做了一套CC++综合測试题.也来測一下你的水平吧(一) 11.设已经有A,B,C ...

  3. Atcoder 水题选做

    为什么是水题选做呢?因为我只会水题啊 ( 为什么是$Atcoder$呢?因为暑假学长来讲课的时候讲了三件事:不要用洛谷,不要用dev-c++,不要用单步调试.$bzoj$太难了,$Topcoder$整 ...

  4. 贪心/构造/DP 杂题选做

    本博客将会收录一些贪心/构造的我认为较有价值的题目,这样可以有效的避免日后碰到 P7115 或者 P7915 这样的题就束手无策进而垫底的情况/dk 某些题目虽然跟贪心关系不大,但是在 CF 上有个 ...

  5. 贪心/构造/DP 杂题选做Ⅱ

    由于换了台电脑,而我的贪心 & 构造能力依然很拉跨,所以决定再开一个坑( 前传: 贪心/构造/DP 杂题选做 u1s1 我预感还有Ⅲ(欸,这不是我在多项式Ⅱ中说过的原话吗) 24. P5912 ...

  6. 贪心/构造/DP 杂题选做Ⅲ

    颓!颓!颓!(bushi 前传: 贪心/构造/DP 杂题选做 贪心/构造/DP 杂题选做Ⅱ 51. CF758E Broken Tree 讲个笑话,这道题是 11.3 模拟赛的 T2,模拟赛里那道题的 ...

  7. LCT裸题泛做

    ①洞穴勘测 bzoj2049 题意:由若干个操作,每次加入/删除两点间的一条边,询问某两点是否连通.保证任意时刻图都是一个森林.(两点之间至多只有一条路径) 这就是个link+cut+find roo ...

  8. [SDOI2016]部分题选做

    听说SDOI蛮简单的,但是SD蛮强的.. 之所以是选做,是因为自己某些知识水平还不到位,而且目前联赛在即,不好花时间去学sa啊之类的.. bzoj4513储能表&bzoj4514数字配对 已写 ...

  9. Splay POJ3468(老题新做)

    A Simple Problem with Integers Time Limit:5000MS     Memory Limit:131072KB     64bit IO Format:%I64d ...

随机推荐

  1. python web1

    ***本篇中的测试均需要使用python3完成. 攻击以下面脚本运作的服务器. 针对脚本的代码逻辑,写出生成利用任意代码执行漏洞的恶意序列的脚本: 打开攻击机端口, 将生成的东西输入网页cookie: ...

  2. 【UE4 设计模式】工厂方法模式 Factory Method Pattern 及自定义创建资源

    概述 描述 又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式,或者多态工厂(Polymorphic Factory)模式 工厂父类负责定义创建产品对象的公共接口,而工厂子类 ...

  3. Vue CLI 5 和 vite 创建 vue3.x 项目以及 Vue CLI 和 vite 的区别

    这几天进入 Vue CLI 官网,发现不能选择 Vue CLI 的版本,也就是说查不到 vue-cli 4 以下版本的文档. 如果此时电脑上安装了 Vue CLI,那么旧版安装的 vue 项目很可能会 ...

  4. k8s replicaset controller 分析(3)-expectations 机制分析

    replicaset controller分析 replicaset controller简介 replicaset controller是kube-controller-manager组件中众多控制 ...

  5. Python课程笔记(十一)

    一.线程与多线程 1.线程与进程 线程指的是 进程(运行中的程序)中单一顺序的执行流. 多个独立执行的线程相加 = 一个进程 多线程程序是指一个程序中包含有多个执行流,多线程是实现并发机制的一种有效手 ...

  6. 『学了就忘』Linux基础 — 17、远程服务器关机及重启时的注意事项

    目录 1.为什么远程服务器不能关机 2.远程服务器重启时需要注意两点 3.不要在服务器访问高峰运行高负载命令 4.远程配置防火墙时不要把自己踢出服务器 5.指定合理的密码规范并定期更新 6.合理分配权 ...

  7. js this指向汇总

    this指向 普通函数  window 定时器函数         window 事件函数 事件源 箭头函数 父function中的this,没有就是window 对象函数 对象本身 构造函数 实例化 ...

  8. 关于ENSP错误代码的常见问题

    1.最适合ensp运行的环境是win7,在win7上运行基本不会出什么大问题(ensp370+virtualbox4.2.8) 2.如果需要重新安装,最好把旧版本清除干净,ensp+virtualbo ...

  9. svg的animate动画动态加载删除遇到删除animate后再次加载的animate动画没有效果问题

    svg上有多个圆圈,当选中特定圆圈后给其加上animate动画效果,并把其他圆圈的animate效果去除. 第一次选择一个点实现动画效果完全达到效果,因为是第一次所以不需要把其他圆圈的animate子 ...

  10. LeetCode88 合并有序数组

    1. 这道题为简单题目,但是还有需要好好思考的 2. 首先不能使用额外数组合并,不然就没得后文了 3. nums1后面有0填充,且填充数量正好是n,整个数组大小即m+n能够容纳合并后的数据 4.既然要 ...