KMP算法 AC自动机
KMP算法
poj3461 Oulipo
题目大意:模板题。
思路:模板题。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int f[10010],ans; char s1[10000],s2[1000000]; void prework() { int i,j,n; n=strlen(s1); f[0]=f[1]=0; for (i=1;i<n;++i) { j=f[i]; while(j&&s1[i]!=s1[j]) j=f[j]; f[i+1]=(s1[j]==s1[i]?j+1:0); } } void work() { int n,m,i,j; n=strlen(s1);m=strlen(s2); j=0; for (i=0;i<m;++i) { while(j&&s1[j]!=s2[i]) j=f[j]; if (s1[j]==s2[i]) ++j; if (j==n) ++ans; } } int main() { int i,n; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%*c%s%*c%s",&s1,&s2); prework(); ans=0;work(); printf("%d\n",ans); } }
poj2752 Seek the Name, Seek the Fame
题目大意:求一个串前缀和后缀完全一样的所有长度。
思路:维护出f数组后,从最后一位循环一下,如果ch[j]==ch[f[j]],f[j]+1就是一个可行的长度,j=f[j],一直循环下去。突然发现f数组很神奇。其实我们能保证前f[j]+1个元素和以j为结尾的f[j]+1个元素一定是相等的。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char ch[400000]; int f[400001]={0},ans[400001]={0},l; void prework() { int i,j; f[0]=f[1]=0; for (i=1;i<l;++i) { j=f[i]; while(j&&ch[i]!=ch[j]) j=f[j]; f[i+1]=(ch[i]==ch[j]?j+1:0); } } void work() { int j; j=l-1; while(j&&ch[j]==ch[f[j]]) { ans[++ans[0]]=f[j]+1; j=f[j]; } } int main() { int i,j,n; while(scanf("%s",&ch)==1) { ans[0]=0;l=strlen(ch); ans[++ans[0]]=l; prework(); work(); for (i=ans[0];i;--i) printf("%d ",ans[i]); printf("\n"); } }
poj2406 Power Strings
题目大意:子串重复出现次数最多且重复后恰好是原串的长度。
思路:如果能重复后恰是原串的长度,那么l%(l-f[l])==0,如果能==0,则答案就是l/(l-f[l]),否则答案为1。这里一定是l和f[l],否则会出很多奇怪的问题。。。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char ch[1000000]; int f[1000001]={0},l; void prework() { int i,j; f[0]=f[1]=0; for (i=1;i<l;++i) { j=f[i]; while(j&&ch[i]!=ch[j]) j=f[j]; f[i+1]=(ch[i]==ch[j]?j+1:0); } } int main() { int i,j,ans; while(scanf("%s",&ch)==1) { l=strlen(ch); if (ch[0]=='.') break; prework(); if (l%(l-f[l])==0) ans=l/(l-f[l]); else ans=1; printf("%d\n",ans); } }
poj1961 Period
题目大意:找出字符串中前缀长度为i(2<=i<=n)的字符串能被某个字符串重复k(k>1)遍得到,则输出这个i和k,按i的升序输出。
思路:其实就上上一题的小小加强版。循环一下i就可以了,从2到n,如果i%(i-f[i])==0则符合,k为i/(i-f[i])。其实不难理解,因为i-f[i]就是错位部分的长度,而之前的每一个这么长的部分都是相等的(如果是%==0的话)。这里有一点就是k>1,所以f[i]>0才能符合。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char ch[1000000]; int f[1000001]={0},n; void prework() { int i,j; f[0]=f[1]=0; for (i=1;i<n;++i) { j=f[i]; while(j&&ch[j]!=ch[i]) j=f[j]; f[i+1]=(ch[i]==ch[j]?j+1:0); } } int main() { int i,j,kk=0; while(scanf("%d",&n)==1) { if (!n) break; scanf("%s",&ch); i=0;prework(); printf("Test case #%d\n",++kk); for (i=2;i<=n;++i) if (f[i]&&i%(i-f[i])==0) printf("%d %d\n",i,i/(i-f[i])); printf("\n"); } }
bzoj3670 动物园
题目大意:找出字符串中前缀和后缀一样并且满足长度小于等于串长一半的每一个位置的个数。
思路:据说是扩展kmp的裸题。做两遍递推,这里修正了一下kmp的写法,这样的做法更容易变通到其他题上。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxnode 1000005 #define LL long long #define p 1000000007 using namespace std; char ss[maxnode]; int f[maxnode],cnt[maxnode]={0},num[maxnode]={0}; int main() { int n,i,j,l;LL ans; scanf("%d",&n); while(n--) { memset(f,0,sizeof(f)); memset(cnt,0,sizeof(cnt)); scanf("%s",&ss);l=strlen(ss); f[0]=f[1]=j=0;ans=1;cnt[1]=1; for (i=1;i<l;++i) { while(j&&ss[i]!=ss[j]) j=f[j]; f[i+1]=(ss[i]==ss[j] ? ++j : 0); cnt[i+1]=cnt[f[i+1]]+1; } j=0; for (i=0;i<l;++i) { while(j&&ss[i]!=ss[j]) j=f[j]; if (ss[i]==ss[j]) ++j; while(j&&j>(i+1>>1)) j=f[j]; ans=ans*(cnt[j]+1)%p; } printf("%I64d\n",ans); } }
bzoj3620 似乎在梦中见过的样子
题目大意:求一个字符串的满足形式如A+B+A(LA>=k,LB>=1)的子串的个数。
思路:用拓展kmp做的,但是因为可能这个子串在字符串的中间位置,所以要穷举起点,然后再做,注意累加答案的时候不能统计上位置相同但分割不同的方案,所以每次找到一个合法位置,给答案加1就可以了。(判断合法位置比较麻烦,要保证A、B的长度都符合)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 15005 #define LL long long using namespace std; char ss[maxm],si[maxm]; int f[maxm]={0}; int main(){ int n,m,i,j,k,l,sta,ans=0; scanf("%s%d",&si,&k); l=strlen(si); for (sta=0;sta<l;++sta){ if (l-sta<=2*k) break; for (i=sta;i<l;++i) ss[i-sta]=si[i]; f[0]=f[1]=j=0; for (i=1;i<l-sta;++i){ while(j&&ss[i]!=ss[j]) j=f[j]; f[i+1]=(ss[i]==ss[j] ? ++j : 0); }j=0; for (i=0;i<l-sta;++i){ while(j&&ss[i]!=ss[j]) j=f[j]; if (ss[i]==ss[j]) ++j; while(j&&j > (i>>1)) j=f[j]; ans+=(j>=k); } }printf("%d\n",ans); }
noi模拟赛第五场T2(!!!)
题目大意:在线,支持在x版本后面加一个字母,问这个串可以是最短的K重复后的前缀,输出K的长度。
思路:如果是线性的串,就是维护kmp之后,l-f[l]就是答案,但是如果看做操作树的话,复杂度会退化,考虑动态维护trans(x,c)表示x这个位置+c这个字母之后转移到的位置。每次加一个字母的时候,看j=trans(x,c),dep[i]-dep[j]就是答案。然后更新相应的位置,trans(x,c)=i,因为每次维护的是一条链上每个点的trans信息,所以也要可持久化,把i操作后x对应的trans的根相应的改成新的,同时i操作后i对应的trans的根就是j对应的根。
注意:两棵可持久化线段树的下标要搞清楚。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 300005 #define M 12000005 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} struct use{int l,r,v;}tr[M],seg[M]; int tt=0,st=0,rt[N]={0},dep[N]={0}; int ask(int i,int l,int r,int x){ if (l==r) return tr[i].v; int mid=(l+r)>>1; if (x<=mid) return ask(tr[i].l,l,mid,x); else return ask(tr[i].r,mid+1,r,x); } void tch(int &i,int j,int l,int r,int x,int y){ tr[i=++tt]=tr[j]; if (l==r){tr[i].v=y;return;} int mid=(l+r)>>1; if (x<=mid) tch(tr[i].l,tr[j].l,l,mid,x,y); else tch(tr[i].r,tr[j].r,mid+1,r,x,y); } int ssk(int i,int l,int r,int x){ if (l==r) return seg[i].v; int mid=(l+r)>>1; if (x<=mid) return ssk(seg[i].l,l,mid,x); else return ssk(seg[i].r,mid+1,r,x); } void sch(int &i,int j,int l,int r,int x,int y){ seg[i=++st]=seg[j]; if (l==r){seg[i].v=y;return;} int mid=(l+r)>>1; if (x<=mid) sch(seg[i].l,seg[j].l,l,mid,x,y); else sch(seg[i].r,seg[j].r,mid+1,r,x,y); } int main(){ freopen("string.in","r",stdin); freopen("string.out","w",stdout); int n,m,type,xi,ci,i,j,k,la=0; n=in();m=in();type=in(); for (i=1;i<=n;++i){ xi=in();ci=in(); if (type){xi^=la;ci^=la;} dep[i]=dep[xi]+1; j=ask(rt[xi],0,n,xi); k=ssk(j,1,m,ci); printf("%d\n",la=dep[i]-dep[k]); sch(j,j,1,m,ci,i); tch(rt[i],rt[xi],0,n,xi,j); tch(rt[i],rt[i],0,n,i,ask(rt[i],0,n,k)); } }
AC自动机
cogs1913
题目大意:模板题(模板无重复)。
思路:模板题。
#include<iostream> #include<cstdio> #include<cstring> #define sigma_size 26 #define maxnode 100000 #define len 100000 using namespace std; struct Trie{ int ch[maxnode][sigma_size],val[maxnode],last[maxnode],f[maxnode],sz,num,cnt[100]; void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); memset(cnt,0,sizeof(cnt)); } int idx(char ch){return ch-'a';} void insert(char *s) { int u=0,n=strlen(s),i; for (i=0;i<n;++i) { int c=idx(s[i]); if (!ch[u][c]) { memset(ch[++sz],0,sizeof(ch[sz])); val[sz]=0;ch[u][c]=sz; } u=ch[u][c]; } val[u]=++num; } void prework() { int que[100001]={0},head,tail,u,r,v,i; f[0]=0;head=tail=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=last[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u; v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; last[u]=val[f[u]]?f[u]:last[f[u]]; } } } void print(int j) { if (j) { ++cnt[val[j]]; print(last[j]); } } void find(char *s) { int u,n,i,j=0; n=strlen(s); for (i=0;i<n;++i) { u=idx(s[i]); j=ch[j][u]; if (val[j]) print(j); else if (last[j]) print(last[j]); } } }ac; char sta[51][50],ss[10000000]; int main() { freopen("ACautomata.in","r",stdin); freopen("ACautomata.out","w",stdout); int n,i,j; scanf("%d",&n); ac.init(); for (i=1;i<=n;++i) { scanf("%*c%s",&sta[i]); ac.insert(sta[i]); } ac.prework(); scanf("%*c%s",&ss); ac.find(ss); for (i=1;i<=n;++i) printf("%s %d\n",sta[i],ac.cnt[i]); fclose(stdin); fclose(stdout); }
bzoj3172 单词
题目大意:给定一篇文章中的n个单词,文章就是把他们连起来(单词之间有空格),求每个单词出现的次数。
思路:比较裸的ac自动机,注意单词可重就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define maxnode 1000005 #define maxsiz 27 #define maxn 205 using namespace std; char ss[maxnode+maxn],si[maxn][100005]; int id[maxn]={0},ll=0; struct use{ int ch[maxnode][maxsiz],val[maxnode],last[maxnode],f[maxnode],sz,num, cnt[maxnode],que[maxnode]; int init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); memset(cnt,0,sizeof(cnt)); } int idx(char x){return x=='#' ? 26 : x-'a';} void insert(char *s,int kk) { int n,u=0,v,i;n=strlen(s); for (i=0;i<n;++i) { v=idx(s[i]); if (!ch[u][v]) { memset(ch[++sz],0,sizeof(ch[sz])); ch[u][v]=sz;val[sz]=0; } u=ch[u][v]; } if (!val[u]) val[u]=++num; id[kk]=val[u]; } void prework() { int i,j,head,tail,u,v,r; f[0]=0;head=tail=0; for (i=0;i<maxsiz;++i) { u=ch[0][i]; if (u){que[++tail]=u;f[u]=last[u]=0;} } while(head<tail) { r=que[++head]; for (i=0;i<maxsiz;++i) { u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i];last[u]=val[f[u]] ? f[u] : last[f[u]]; } } } void print(int u) { if (u){++cnt[val[u]];print(last[u]);} } void find(char *s,int l) { int i,j,u=0,v; for (i=0;i<l;++i) { j=idx(s[i]);u=ch[u][j]; if (val[u]) print(u); else if (last[u]) print(last[u]); } } }ac; int main() { int n,i,j,l; scanf("%d",&n);ac.init(); for (i=1;i<=n;++i) { scanf("%s",&si[i]);l=strlen(si[i]); for (j=0;j<l;++j) ss[ll++]=si[i][j]; ac.insert(si[i],i);ss[ll++]='#'; }ac.prework();ac.find(ss,ll); for (i=1;i<=n;++i) printf("%d\n",ac.cnt[id[i]]); }
poj1204Word Puzzles
题目大意:给一个字母矩阵,然后给定一些模板,找这些模板在矩阵中的位置,这些模板可以在矩阵中八个方向排列。
思路:裸的ac自动机,但是find的时候要八个方向。做的时候把模板倒着建的,这样找到匹配就是起始位置了,然后方向什么的就随便搞一下。
注意不要在结构体里开太大的数组,不然会很愉悦的RE。。。在很奇怪的地方RE。。。
#include<iostream> #include<cstdio> #include<cstring> #define sigma_size 26 #define maxnode 1000000 using namespace std; int l,c,w,que[1000001]={0}; char ss[1000][1000],ch[1001][1000]; struct Trie{ int ch[maxnode][sigma_size],f[maxnode],last[maxnode],val[maxnode],sz,num,cnt[1001][3]; void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); memset(cnt,0,sizeof(cnt)); } int idx(char ch){return ch-'A';} void insert(char *s) { int n,i,u=0,c; n=strlen(s); for (i=n-1;i>=0;--i) { c=idx(s[i]); if (!ch[u][c]) { memset(ch[++sz],0,sizeof(ch[sz])); val[sz]=0;ch[u][c]=sz; } u=ch[u][c]; } val[u]=++num; } void prework() { int i,j,u,r,v,head,tail; f[0]=0;head=tail=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=last[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; last[u]=(val[f[u]]?f[u]:last[f[u]]); } } } void print(int j,int x,int y,int kk) { if (j) { cnt[val[j]][0]=x;cnt[val[j]][1]=y; cnt[val[j]][2]=kk;print(last[j],x,y,kk); } } void find() { int i,j,k,u,v; for (i=0;i<l;++i) { u=0; for (j=0;j<c;++j) { v=idx(ss[i][j]); u=ch[u][v]; if (val[u]) print(u,i,j,6); else if (last[u]) print(last[u],i,j,6); } } for (i=0;i<l;++i) { u=0; for (j=c-1;j>=0;--j) { v=idx(ss[i][j]); u=ch[u][v]; if (val[u]) print(u,i,j,2); else if (last[u]) print(last[u],i,j,2); } } for (j=0;j<c;++j) { u=0; for (i=0;i<l;++i) { v=idx(ss[i][j]); u=ch[u][v]; if (val[u]) print(u,i,j,0); else if (last[u]) print(last[u],i,j,0); } } for (j=0;j<c;++j) { u=0; for (i=l-1;i>=0;--i) { v=idx(ss[i][j]); u=ch[u][v]; if (val[u]) print(u,i,j,4); else if (last[u]) print(last[u],i,j,4); } } for (j=1;j<=l+c-1;++j) { u=0;i=min(j,l)-1;k=j-i-1; while(i>=0&&k<c) { v=idx(ss[i][k]); u=ch[u][v]; if (val[u]) print(u,i,k,5); else if (last[u]) print(last[u],i,k,5); --i;++k; } } for (j=1;j<=l+c-1;++j) { u=0;k=min(c,j)-1;i=j-k-1; while(i<l&&k>=0) { v=idx(ss[i][k]); u=ch[u][v]; if (val[u]) print(u,i,k,1); else if (last[u]) print(last[u],i,k,1); ++i;--k; } } for (j=1;j<=l+c-1;++j) { u=0;i=min(l,j)-1;k=i-j+c; while(i>=0&&k>=0) { v=idx(ss[i][k]); u=ch[u][v]; if (val[u]) print(u,i,k,3); else if (last[u]) print(last[u],i,k,3); --i;--k; } } for (j=1;j<=l+c-1;++j) { u=0;i=max(1,j+1-c)-1;k=i-j+c; while(i<l&&k<c) { v=idx(ss[i][k]); u=ch[u][v]; if (val[u]) print(u,i,k,7); else if (last[u]) print(last[u],i,k,7); ++i;++k; } } } }ac; int main() { int i,j; scanf("%d%d%d",&l,&c,&w); for (i=0;i<l;++i) scanf("%*c%s",&ss[i]); for (i=1;i<=w;++i) { scanf("%*c%s",&ch[i]); ac.insert(ch[i]); } ac.prework(); ac.find(); for (i=1;i<=w;++i) printf("%d %d %c\n",ac.cnt[i][0],ac.cnt[i][1],ac.cnt[i][2]+'A'); }
cogs1376||bzoj2434 阿狸的打字机
题目大意:给定一个序列(由小写字母和B、P组成),如果是P就将有的字母组成一个新的字符串,如果是B就删掉栈顶的字母。查询给定x、y,查第x个字符串在y中出现的次数。
思路:自己写的话应该只能拿最暴力的40分。这里要求对ac自动机里失配边十分了解。我们边做便建立了一个ac自动机。考虑暴力的做法:从y的每一个节点不断root,如果能到x的结尾点就+1。在失配树(失配边的反向边组成的树)里,就是统计x的子树里有多少个y的节点。这样我们建好一颗失配树,做dfs序,在扫一遍给出的串,像一开始那样,不断的走,如果是结点的话,就给树状数组中的这个位置+1;如果P就看查询里y(排序,把y都搞到一起)为当前字串的,对x(之前记录下每种串的节点位置)进行查询;如果是B就把这个位置-1。
Fail树的性质:X是Y(都是指AC自动机上的节点代表的子串)的子串,则在Fail树中,Y上的点一定有在X的子树中的。(Y顺着失配的失配的...往上走,一定有X,所以反过来后,Y的结点(Y中以此位置结尾的字串与X相等)一定有在X子树中的)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1000010 #define sigma_size 26 using namespace std; int que[maxnode]={0},tot=0,tt=0,tim[maxnode][2]={0},cc[maxnode]={0},ans[maxnode]={0},next[maxnode]={0},point[maxnode]={0},sz=0; struct use{ int st,en; }edge[maxnode]; struct used{ int xi,yi,po; }ask[maxnode]; struct Trie{ int ch[maxnode][sigma_size],val[maxnode],last[maxnode],f[maxnode],fa[maxnode],num,po[maxnode]; int lowbit(int x){return x&(-x);} void bins(int x,int xx) { for (;x<=tt;x+=lowbit(x)) cc[x]+=xx; } int bask(int l,int r) { int sum=0; --l; for (;r;r-=lowbit(r)) sum+=cc[r]; for (;l;l-=lowbit(l)) sum-=cc[l]; return sum; } void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); } int idx(char ch){return ch-'a';} void insert(char *s) { int n,i,j,u=0,v; n=strlen(s);fa[0]=0; for (i=0;i<n;++i) { if (s[i]>='a'&&s[i]<='z') { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0;fa[sz]=u; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } if (s[i]=='P') { val[u]=++num;po[num]=u; } if (s[i]=='B') u=fa[u]; } } void prework() { int n,i,j,u,v,r,head,tail; head=tail=0;f[0]=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;last[u]=f[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; last[u]=(val[f[u]]?f[u]:last[f[u]]); } } } void work(char *s,int up) { int n,i,j,u=0,now=1; n=strlen(s); for (i=0;i<n;++i) { if (s[i]>='a'&&s[i]<='z') { u=ch[u][idx(s[i])];bins(tim[u][0],1); } if (s[i]=='B') { bins(tim[u][0],-1);u=fa[u]; } if (s[i]=='P') { while (val[u]==ask[now].yi&&now<=up) { ans[ask[now].po]=bask(tim[po[ask[now].xi]][0],tim[po[ask[now].xi]][1]); ++now; } } } } }ac; char ch[maxnode]; bool visit[maxnode]={false}; int my_comp(const used &x,const used &y) { if (x.yi<y.yi) return 1; else return 0; } void add(int st,int en) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en; } void build(int u) { int i; visit[u]=true;tim[u][0]=++tt; for (i=point[u];i;i=next[i]) if (!visit[edge[i].en]) build(edge[i].en); tim[u][1]=tt; } int main() { freopen("noi2011_type.in","r",stdin); freopen("noi2011_type.out","w",stdout); int i,j,n; scanf("%s",&ch); ac.insert(ch); ac.prework(); for (i=1;i<=sz;++i) add(ac.f[i],i); build(0); scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%d%d",&ask[i].xi,&ask[i].yi); ask[i].po=i; } sort(ask+1,ask+n+1,my_comp); ac.work(ch,n); for (i=1;i<=n;++i) printf("%d\n",ans[i]); fclose(stdin); fclose(stdout); }
bzoj1468 文本生成器
题目大意:给定N个模板串,求长度为M的串中出现模板串的个数。
思路:建立一个ac自动机,标记一下每个单词的结尾,然后dp一下,用记忆化搜索dfs(u(当前结点),m(本次未选时剩余长度))实现,如果是单词结尾就给答案加上26^(m-1),否则就dfs下去。
#include<iostream> #include<cstdio> #include<cstring> #define sigma_size 26 #define maxnode 10000 #define P 10007 #define len 110 using namespace std; int mi[len]={0},que[maxnode]={0},dp[maxnode][len]; char ss[len]; struct Trie{ int ch[maxnode][sigma_size],val[maxnode],f[maxnode],sz,num; int idx(char u){return u-'A';} void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); } void insert(char *s) { int n,i,j,u=0,v; n=strlen(s);val[0]=0; for (i=0;i<n;++i) { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } val[u]=1; } void prework() { int i,j,u,v,r,head,tail; head=tail=0;f[0]=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u) { ch[r][i]=ch[f[r]][i];continue; } que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; val[u]|=val[f[u]]; } } } int dfs(int u,int m) { int i,j,ans=0; if (!m) return 0; if (dp[u][m]>=0) return dp[u][m]; for (i=0;i<sigma_size;++i) { j=ch[u][i]; if (!val[j]) ans=(ans+dfs(j,m-1))%P; else ans=(ans+mi[m-1])%P; } dp[u][m]=ans; return ans; } }ac; int main() { int n,m,i,j,ans; for (i=0;i<maxnode;++i) for (j=0;j<len;++j) dp[i][j]=-1; scanf("%d%d",&n,&m); mi[0]=1; for (i=1;i<=m;++i) mi[i]=(mi[i-1]*26)%P; ac.init(); for (i=1;i<=n;++i) { scanf("%*c%s",&ss); ac.insert(ss); } ac.prework(); ans=ac.dfs(0,m)%P; printf("%d\n",ans); }
cogs1468 文本生成器
题目大意:同上。
思路:这道题目中m的长度<=10^6,所以如果还是dfs的话,就炸掉了。后来得知是矩阵乘法。我们根据trie建一个矩阵mat[][],mat[i][j]表示i到j走1步并且不碰到单词结点的种数,矩阵乘一下,然后我们发现它的m次方中mat[0][i]的和就是所有不包含单词的个数,用总的个数减去这个就可以了。这样,我们就可以处理很多这种文章很长但是模板很小的ac自动机上的dp了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define sigma_size 26 #define maxnode 65 #define P 10007 #define len 1000010 using namespace std; struct matr{ int num[maxnode][maxnode]; }mat,m1,m2; int mi[len]={0},que[maxnode]={0}; char ss[len]; struct Trie{ int ch[maxnode][sigma_size],val[maxnode],f[maxnode],sz,num; int idx(char u){return u-'A';} void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); } void insert(char *s) { int n,i,j,u=0,v; n=strlen(s);val[0]=0; for (i=0;i<n;++i) { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } val[u]=1; } void prework() { int i,j,u,v,r,head,tail; head=tail=0;f[0]=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u) { ch[r][i]=ch[f[r]][i];continue; } que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; val[u]|=val[f[u]]; } } } void prematrix() { int i,j,u; for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) mat.num[i][j]=0; for (i=0;i<=sz;++i) for (j=0;j<sigma_size;++j) { u=ch[i][j]; if (!val[i]&&!val[u]) ++mat.num[i][u]; } } void matrixmult(matr mu1,matr mu2,matr &mu3) { int i,j,k; for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) { mu3.num[i][j]=0; for (k=0;k<=sz;++k) mu3.num[i][j]=(mu3.num[i][j]+mu1.num[i][k]*mu2.num[k][j])%P; } } void matrix(int m) { int i,j; if (m==1) { for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) m1.num[i][j]=mat.num[i][j]; return; } matrix(m/2); matrixmult(m1,m1,m2); if (m%2) matrixmult(m2,mat,m1); else swap(m1,m2); } }ac; int main() { freopen("textgen.in","r",stdin); freopen("textgen.out","w",stdout); int n,m,i,j,ans=0; scanf("%d%d",&n,&m); mi[0]=1; for (i=1;i<=m;++i) mi[i]=(mi[i-1]*26)%P; ac.init(); for (i=1;i<=n;++i) { scanf("%*c%s",&ss); ac.insert(ss); } ac.prework(); ac.prematrix(); ac.matrix(m); for (i=0;i<=ac.sz;++i) ans=(ans+m1.num[0][i])%P; ans=(mi[m]-ans+P)%P; printf("%d\n",ans); fclose(stdin); fclose(stdout); }
poj2778 DNA Sequence
题目大意:给定n个DNA序列,求长度为m的不包含这些给定的DNA序列的种类数。(m<=2000000000)。
思路:和上一个题一样,面对这么大的m,我们还是要用到矩阵。这里矩阵里∑mat[0][i]就是答案了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 110 #define P 100000 #define sigma_size 4 using namespace std; int que[maxnode]={0}; char ss[maxnode]; struct matr{ long long num[maxnode][maxnode]; }mat,m1,m2; struct Trie{ int ch[maxnode][sigma_size],val[maxnode],f[maxnode],sz,num; int idx(char s) { if (s=='A') return 0; if (s=='C') return 1; if (s=='G') return 2; if (s=='T') return 3; } void init() { sz=num=0; memset(ch[0],0,sizeof(ch[0])); } void insert(char *s) { int n,i,j,u=0,v; n=strlen(s); for (i=0;i<n;++i) { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } val[u]=1; } void prework() { int n,i,j,u,r,v,head,tail; head=tail=0;u=0;f[u]=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u) { ch[r][i]=ch[f[r]][i];continue; } que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; val[u]|=val[f[u]]; } } } void prematrix() { int i,j,u; for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) mat.num[i][j]=0; for (i=0;i<=sz;++i) for (j=0;j<sigma_size;++j) { u=ch[i][j]; if (!val[u]&&!val[i]) ++mat.num[i][u]; } } void matrixmult(matr mu1,matr mu2,matr &mu3) { int i,j,k; for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) { mu3.num[i][j]=0; for (k=0;k<=sz;++k) mu3.num[i][j]=(mu3.num[i][j]+mu1.num[i][k]*mu2.num[k][j])%P; } } void matrix(int m) { int i,j; if (m==1) { for (i=0;i<=sz;++i) for (j=0;j<=sz;++j) m1.num[i][j]=mat.num[i][j]; return; } matrix(m/2); matrixmult(m1,m1,m2); if (m%2) matrixmult(m2,mat,m1); else swap(m1,m2); } }ac; int main() { int i,j,n,m,ans=0; scanf("%d%d",&n,&m); ac.init(); for (i=1;i<=n;++i) { scanf("%*c%s",&ss); ac.insert(ss); } ac.prework(); ac.prematrix(); ac.matrix(m); for (i=0;i<=ac.sz;++i) ans=(int)((long long)ans+m1.num[0][i])%P; printf("%d\n",ans); }
poj1625 Censored!
题目大意:同上。
思路:因为没有mod,所以高精度一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 10000 using namespace std; char ha[50],ss[20]; int n,que[maxnode]={0}; struct use{ int l,num[500]; }dp[maxnode][51]; bool vis[maxnode][51]={false}; struct use jia(struct use a,struct use b) { struct use c; int i,j; j=0;c.l=max(a.l,b.l); memset(c.num,0,sizeof(c.num)); for (i=1;i<=c.l;++i) { c.num[i]=j+a.num[i]+b.num[i]; j=c.num[i]/10; c.num[i]%=10; } if (j) c.num[++c.l]=j; return c; } struct Trie{ int ch[maxnode][50],f[maxnode],val[maxnode],sz,num; void init() { sz=num=0;val[0]=0; memset(ch[0],0,sizeof(ch[0])); } int idx(char ss) { int l,r,mid; l=0;r=n-1; while(l!=r) { mid=(l+r)/2; if (ss<=ha[mid]) r=mid; else l=mid+1; } return l; } void insert(char *s) { int l,i,j,u=0,v; l=strlen(s); for (i=0;i<l;++i) { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } val[u]=1; } void prework() { int head,tail,i,j,r,u,v; f[0]=0;head=tail=0; for (i=0;i<n;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<n;++i) { u=ch[r][i]; if (!u) { ch[r][i]=ch[f[r]][i];continue; } que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; val[u]|=val[f[u]]; } } } }ac; struct use work(int u,int m) { struct use c; int i,j,v; c.l=1;memset(c.num,0,sizeof(c.num)); c.num[1]=1; if (!m) return c; if (vis[u][m]) return dp[u][m]; vis[u][m]=true;c.num[1]=0; for (i=0;i<n;++i) { v=ac.ch[u][i]; if (!ac.val[v]) c=jia(c,work(v,m-1)); } dp[u][m]=c; return c; } int main() { int m,p,i,j; scanf("%d%d%d%*c",&n,&m,&p); gets(ha); sort(ha+0,ha+n); ac.init(); for (i=1;i<=p;++i) { scanf("%s%*c",&ss); ac.insert(ss); } ac.prework(); use ans; ans=work(0,m); for (i=ans.l;i;--i) printf("%d",ans.num[i]); printf("\n"); }
ZOJ3228 Searching the String
题目大意:很多模板串,在文本中找这些模板串出现的次数(可以重复和不可以重复)。
思路:我们保存一下每一种串上一次在文本串中的位置,如果不重复就+1。因为这一题模板串可以重复,一开始用next数组把一个节点对应的所有模板串都加进去了,然后tle的很惨,后来发现一个点对应的模板串唯一,只是有两种状态(即两种查询),所以用个数组保存一下,最后输出的时候对应过去就可以了。
#include<iostream> #include<cstdio> #include<cstring> #define sigma_size 26 #define maxnode 600010 #define len 100001 using namespace std; int kk[len]={0},que[maxnode]={0},ll[len]={0}; char ss[len]; struct Trie{ int ch[maxnode][sigma_size],f[maxnode],val[maxnode],last[maxnode],sz,num,cnt[maxnode][3],next[len],posi[len]; bool uu[maxnode][2]; int idx(char s){return s-'a';} void init() { int i; memset(ch[0],0,sizeof(ch[0])); for (i=0;i<maxnode;++i) { cnt[i][0]=0;cnt[i][1]=0;cnt[i][2]=-1; } memset(uu,false,sizeof(uu)); sz=num=0;val[0]=0; } void insert(char *s) { int n,i,j,u=0,v; n=strlen(s); for (i=0;i<n;++i) { v=idx(s[i]); if (!ch[u][v]) { ch[u][v]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); } u=ch[u][v]; } val[u]=++num;ll[num]=n;uu[u][kk[num]]=true;posi[num]=u; } void prework() { int i,j,u,v,r,head,tail; f[0]=0;head=tail=0; for (i=0;i<sigma_size;++i) { u=ch[0][i]; if (u) { que[++tail]=u;f[u]=last[u]=0; } } while(head<tail) { r=que[++head]; for (i=0;i<sigma_size;++i) { u=ch[r][i]; if (!u) {ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i]; last[u]=(val[f[u]]?f[u]:last[f[u]]); } } } void print(int i,int j) { int k; if (j) { if (uu[j][0]) ++cnt[j][0]; if (uu[j][1]&&cnt[j][2]+ll[val[j]]<=i) { ++cnt[j][1];cnt[j][2]=i; } print(i,last[j]); } } void find(char *s) { int n,i,j,u=0; n=strlen(s); for (i=0;i<n;++i) { j=idx(s[i]); u=ch[u][j]; if (val[u]) print(i,u); else if (last[u]) print(i,last[u]); } } }ac; int main() { int n,i,j=0; char s[10]; while(scanf("%s",&ss)==1) { scanf("%d",&n);++j; ac.init(); for (i=1;i<=n;++i) { scanf("%d%s",&kk[i],&s); ac.insert(s); } scanf("%*c"); ac.prework(); ac.find(ss); printf("Case %d\n",j); for (i=1;i<=n;++i) printf("%d\n",ac.cnt[ac.posi[i]][kk[i]]); printf("\n"); } }
bzoj3530 数数
题目大意:求小于等于n的不包含给定数字串的数字的个数。
思路:ac自动机之后,数位dp。这里dp的时候要格外注意0号节点,因为他没有具体数值的意义,所以我们预处理的数组fi[i][j]表示从前往后的第i+1位数的时候前面都与n相同的个数,gi[i][j]表示从前往后的第i+1位数的时候前面比小的方案数,j都是指的某一个节点。最后答案就是所有节点到n长度的fi[][],gi[][]的和了。
在ac自动机上的数位dp一定要考虑好0号节点(!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2000 #define maxsiz 10 #define len 1000000 #define p 1000000007 #define LL long long using namespace std; char n[maxm],s[maxm]; int fi[maxm][maxm]={0},gi[maxm][maxm]={0},ln; int idx(char x){return x-'0';} struct Trie{ int ch[maxm][maxsiz],f[maxm],match[maxm],que[len+1],sz; void init(){sz=0;memset(ch[0],0,sizeof(ch[0]));} void insert(char *s){ int i,c,u=0,l;l=strlen(s); for (i=0;i<l;++i){ int c=idx(s[i]); if (!ch[u][c]){ memset(ch[++sz],0,sizeof(ch[sz])); match[sz]=0;ch[u][c]=sz; }u=ch[u][c]; }match[u]=1; } void pre(){ int head=0,tail=0,i,j,u,v,r; f[0]=0; for (i=0;i<maxsiz;++i) if (v=ch[0][i]){f[v]=0;que[++tail]=v;} while(head!=tail){ r=que[++head]; for (i=0;i<maxsiz;++i){ u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i];match[u]|=match[f[u]]; } } } int calc(){ int i,j,k,t,u,v,ans=0; for (i=0;i<ln;++i) for (j=0;j<=sz;++j){ t=idx(n[i]); for (k=0;k<t;++k) if (!match[v=ch[j][k]]) gi[i+1][v]=(gi[i+1][v]+fi[i][j])%p; if (!match[v=ch[j][t]])fi[i+1][v]=(fi[i+1][v]+fi[i][j])%p; for (k=0;k<maxsiz;++k) if (!match[v=ch[j][k]]) gi[i+1][v]=(gi[i+1][v]+gi[i][j])%p; if (!j){ if (!i){ for (k=1;k<t;++k) if (!match[v=ch[j][k]]) gi[i+1][v]=(gi[i+1][v]+1)%p; if (!match[v=ch[j][t]])fi[i+1][v]=(fi[i+1][v]+1)%p; }else{ for (k=1;k<maxsiz;++k) if (!match[v=ch[j][k]]) gi[i+1][v]=(gi[i+1][v]+1)%p; } } }for (i=0;i<=sz;++i) ans=((ans+fi[ln][i])%p+gi[ln][i])%p; return ans; } }ac; int main(){ int m,i,j,k,t,u,v;scanf("%s%d",&n,&m); ac.init();ln=strlen(n); for (i=1;i<=m;++i){ scanf("%s",&s);ac.insert(s); }ac.pre();printf("%d\n",ac.calc()); }
也可以写成记忆化搜索的版本,fi[i][j][k]表示到i这个节点、还需要选j位、k表示状态(0表示之前选的没有超过原数、1表示超过了)。但是因为要处理前导零的问题,所以倒着建ac自动机、到这匹配比较好做。这样的话,dp就要多开一维,表示从后往前选数的时候,后面部分是否超过原数,影响之后的上界。同时要注意,搜索结束的标志:1)这个位置已经搜过了;2)当前节点是单词结尾,那么又要考虑有无前导零,如果有前导零,就可以将前面都取为0,从而方案为1,但如果长度为0了,那么如果后面已选的超过原数就是0;3)还能选的长度为0,那么如果没超原数就是1,如果超了就是0。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2000 #define maxsiz 10 #define len 1000000 #define p 1000000007 #define LL long long using namespace std; char n[maxm],s[maxm]; int fi[maxm][maxm][2]={0},gi[maxm][maxm]={0},ln; bool vi[maxm][maxm][2]={false}; int idx(char x){return x-'0';} struct Trie{ int ch[maxm][maxsiz],f[maxm],match[maxm],que[len+1],ze[maxm],sz; void init(){sz=0;memset(ch[0],0,sizeof(ch[0]));} void insert(char *s){ int i,c,u=0,l;l=strlen(s); for (i=l-1;i>=0;--i){ int c=idx(s[i]); if (!ch[u][c]){ memset(ch[++sz],0,sizeof(ch[sz])); match[sz]=ze[sz]=0;ch[u][c]=sz; }u=ch[u][c]; }match[u]=1;ze[sz]=(s[0]=='0'?1:0); } void pre(){ int head=0,tail=0,i,j,u,v,r; f[0]=0; for (i=0;i<maxsiz;++i) if (v=ch[0][i]){f[v]=0;que[++tail]=v;} while(head!=tail){ r=que[++head]; for (i=0;i<maxsiz;++i){ u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} que[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i];match[u]|=match[f[u]]; } } } int calc(int u,int l,int ov){ int i,j,v,up; if (vi[u][l][ov]) return fi[u][l][ov]; vi[u][l][ov]=true; if (match[u]){ fi[u][l][0]=fi[u][l][1]=ze[u]; if (!l) fi[u][l][1]=0; vi[u][l][!ov]=true; return fi[u][l][ov]; }if (!l){ fi[u][l][0]=1;fi[u][l][1]=0; vi[u][l][!ov]=true;return fi[u][l][ov]; }up=(ov ? n[l-1]-'0'-1 : n[l-1]-'0'); for (i=0;i<maxsiz;++i){ v=ch[u][i]; if (i<=up) fi[u][l][ov]=(fi[u][l][ov]+calc(v,l-1,0))%p; else fi[u][l][ov]=(fi[u][l][ov]+calc(v,l-1,1))%p; }return fi[u][l][ov]; } }ac; int main(){ int m,i,j,k,t,u,v;scanf("%s%d",&n,&m); ac.init();ln=strlen(n); for (i=1;i<=m;++i){ scanf("%s",&s);ac.insert(s); }ac.pre();printf("%d\n",ac.calc(0,ln,0)-1); }
bzoj1444 有趣的游戏
题目大意:给定m个字母出现的概率和n个人的字符串(长度都是l),当T时刻出现的字符序列和某个人的匹配,就认为这个人赢了,求每个人赢的概率。
思路:考虑fi[i]表示转移到第i结点的概率,这个点的概率就是sigma 可以更新到这个点的点*pi,更新到这个点就是ac自动机上的关系,但是这里的转移是无序且可以相互更新的,所以gauss消元就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100000 #define M 105 #define LD double #define eps 1e-9 using namespace std; int po[M]={0},n,m,l,que[N]; LD pi[M],ai[M][M]={0},bi[M]={0}; char ss[M]; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct use{ int ch[N][M],val[N],f[N],last[N],sz; void init(){ memset(ch[0],0,sizeof(ch[0])); sz=0;} int idx(char ch){return ch-'A';} void insert(char *s,int id){ int i,j,u,v,d; for (u=i=0;i<l;++i){ v=ch[u][d=idx(s[i])]; if (!v){ v=ch[u][d]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); }u=ch[u][d]; }val[u]=1;po[id]=u;} void pre(){ int u,r,i,j,head=0,tail=0;f[0]=0; for (i=0;i<m;++i){ if (!(u=ch[0][i])) continue; que[++tail]=u;f[u]=last[u]=0; }while(head<tail){ r=que[++head]; for (i=0;i<m;++i){ if (!(u=ch[r][i])){ch[r][i]=ch[f[r]][i];continue;} j=f[r];while(j&&!ch[j][i]) j=f[j]; f[u]=ch[j][i];que[++tail]=u; last[u]=(val[f[u]] ? f[u] : last[f[u]]); } }} void work(){ int i,j,k,u,v;LD ki; for (i=1;i<=sz;++i) ai[i][i]=-1.; ai[0][0]=bi[0]=1.; for (i=0;i<=sz;++i){ if (val[i]) continue; for (j=0;j<m;++j){ if (!(u=ch[i][j])) ai[u][i]-=pi[j]; else ai[u][i]+=pi[j]; } }for (i=0;i<=sz;++i){ if (cmp(ai[i][i],0.)==0) for (j=i+1;j<=sz;++j) if (cmp(ai[j][i],0.)!=0){ for (k=0;k<=sz;++k) swap(ai[i][k],ai[j][k]); swap(bi[i],bi[j]);break;} for (j=i+1;j<=sz;++j){ ki=ai[j][i]/ai[i][i]; for (k=i;k<=sz;++k) ai[j][k]-=ai[i][k]*ki; bi[j]-=bi[i]*ki;} }for (i=sz;i>=0;--i){ ai[i][i]=bi[i]/ai[i][i]; for (j=i-1;j>=0;--j) bi[j]-=ai[i][i]*ai[j][i]; } } }ac; int main(){ int i,j;LD x,y; scanf("%d%d%d",&n,&l,&m);ac.init(); for (i=0;i<m;++i){scanf("%lf%lf",&x,&y);pi[i]=x*1./y;} for (i=1;i<=n;++i){scanf("%s",ss);ac.insert(ss,i);} ac.pre();ac.work(); for (i=1;i<=n;++i){ if (ai[po[i]][po[i]]>eps) printf("%.2f\n",ai[po[i]][po[i]]); else printf("0.00\n");} }
bzoj2553 禁忌(!!!)
题目大意:给定一些模板串,随机生成一个长为l的串,将串分割,分割出最多不重合的模板串作为这个串的代价,求代价的期望。
思路:因为要求最多不重合的作为代价,所以可以贪心,ac自动机后如果到单词节点就要返回根节点重新匹配。因为l很大,所以矩乘,矩阵表示从i节点到j节点的期望,对于儿子节点是单词的,直接从父亲连回根,如果不是就从父亲连向儿子,概率都是1/up。但是我们分割的时候可能有不匹配模板串的地方,所以其实我们要的是矩乘之后长度<=len的期望,所以可以在矩阵中加一维,把每一步到单词节点的期望加过来(其实是到单词节点的父亲就连向这个边了)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define LD long double using namespace std; int sz,up; char ss[N]; struct mat{ LD num[N][N]; void init(){memset(num,0,sizeof(num));} mat operator*(const mat&x)const{ int i,j,k;mat y; for (i=0;i<=sz;++i) for (j=0;j<=sz;++j){ y.num[i][j]=0; for (k=0;k<=sz;++k) y.num[i][j]=y.num[i][j]+num[i][k]*x.num[k][j]; }return y;} }a,b; void mi(int x){ int i;for (i=0;i<=sz;++i) b.num[i][i]=1.; for (;x;x>>=1){ if (x&1) b=b*a; a=a*a;} } struct use{ int ch[N][26],val[N],f[N],que[N]; void init(){ memset(ch[0],0,sizeof(ch[0])); sz=0;} int idx(char ch){return ch-'a';} void insert(char *s){ int i,j,u,v,l;l=strlen(s); for (u=i=0;i<l;++i){ if (!(v=ch[u][j=idx(s[i])])){ ch[u][j]=v=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); }u=ch[u][j]; }val[u]=1;} void pre(){ int i,j,head=0,tail=0,u,v,r; for (f[0]=i=0;i<up;++i){ if (!(u=ch[0][i])) continue; que[++tail]=u;f[u]=0; }while(head<tail){ r=que[++head]; for (i=0;i<up;++i){ if (!(u=ch[r][i])){ch[r][i]=ch[f[r]][i];continue;} j=f[r];while(j&&!ch[j][i]) j=f[j]; que[++tail]=u;f[u]=ch[j][i]; val[u]|=val[f[u]]; } }} void build(){ int i,j,u,v;LD per=1./(LD)up; ++sz;a.num[sz][sz]=1.; for (i=0;i<sz;++i){ if (val[i]) continue; for (j=0;j<up;++j){ if (val[v=ch[i][j]]){ a.num[i][0]+=per; a.num[i][sz]+=per; }else a.num[i][v]+=per; } } } }ac; int main(){ int n,l,i,j;ac.init();a.init();b.init(); scanf("%d%d%d",&n,&l,&up); for (i=1;i<=n;++i){scanf("%s",ss);ac.insert(ss);} ac.pre();ac.build();mi(l); printf("%.10f\n",(double)b.num[0][sz]); }
bzoj2780 Sevenk Love Oimaster
题目大意:给定一些模板串和匹配串,问每个模板串出现在几个匹配串里。
思路:对模板串和匹配串建ac自动机,建出fail树,一个节点子树里的权值和就是这个节点所代表字符串出现的次数,但这里要求出现在几个里,所以要在每个位置+1的时候,给上个位置-1,保证一个字符串只记入一次。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 460005 #define mz 26 using namespace std; char ss[N]; int po[N],fi[N]={0},point[N]={0},next[N],en[N],tot=0,dl[N],dr[N],tt=0,dn[N],la[N]={0}, po1[N]={0},ne1[N],en1[N],n,cc[N]={0}; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} struct uu{ int l,r,po; bool operator<(const uu&x)const{return r<x.r;} }ask[N]; struct use{ int ch[N][mz],f[N],val[N],sz; void init(){memset(ch[sz=0],0,sizeof(ch[0]));} int idx(char ch){return ch-'a';} void insert(char *s,int x){ int i,j,u=0,l;l=strlen(s); for (i=0;i<l;++i){ j=idx(s[i]); if (!ch[u][j]){ ch[u][j]=++sz;val[sz]=0; memset(ch[sz],0,sizeof(ch[sz])); }u=ch[u][j]; if (x<=n){ne1[++tot]=po1[u];po1[u]=tot;en1[tot]=x;} }val[u]=1;po[x]=u; } void pre(){ int r,u,v,head,tail,i,j; head=tail=0;f[0]=0; for (i=0;i<mz;++i){ u=ch[0][i]; if (u){fi[++tail]=u;add(0,u);f[u]=0;} }while(head<tail){ r=fi[++head]; for (i=0;i<mz;++i){ u=ch[r][i]; if (!u){ch[r][i]=ch[f[r]][i];continue;} fi[++tail]=u;v=f[r]; while(v&&!ch[v][i]) v=f[v]; f[u]=ch[v][i];add(f[u],u); } } } void dfs(int u){ int i;dn[dl[u]=++tt]=u; for (i=point[u];i;i=next[i]) dfs(en[i]); dr[u]=tt;} }ac; int lowbit(int x){return x&(-x);} void ins(int x,int y){for (;x<=tt;x+=lowbit(x)) cc[x]+=y;} int query(int x,int y){ int sum=0; for (--x;x;x-=lowbit(x)) sum-=cc[x]; for (;y;y-=lowbit(y)) sum+=cc[y]; return sum;} int main(){ int q,i,j,l=0;ac.init(); scanf("%d%d",&n,&q); for (i=1;i<=n;++i){ scanf("%s",ss);ac.insert(ss,i); }for (i=1;i<=q;++i){ scanf("%s",ss);ac.insert(ss,i+n); }tot=0;ac.pre();ac.dfs(0); for (i=1;i<=q;++i) ask[i]=(uu){dl[po[i+n]],dr[po[i+n]],i}; sort(ask+1,ask+q+1); memset(fi,0,sizeof(fi)); for (l=i=1;i<=tt;++i){ for (j=po1[dn[i]];j;j=ne1[j]){ if (la[en1[j]]) ins(la[en1[j]],-1); ins(i,1);la[en1[j]]=i; }for(;l<=q&&i==ask[l].r;++l) fi[ask[l].po]=query(ask[l].l,ask[l].r); }for (i=1;i<=q;++i) printf("%d\n",fi[i]); }
bzoj2754 喵星球上的点名
题目大意:给定每个人的名字和姓,还有每次点名的字符串,点名的字符串是一个人的名字或姓的字串,这个人就要答到,求每个点名的字符串有几个人答到和每个人要答几次到。
思路:建出ac自动机(字符集太大,所以要用map)和fail树,对于第一问:和上一题一样;第二问:还是在fail树上,给每个点名的串的最后一个字符所在的节点的dfs序位置+1,求每个人名字的所有节点到根的路径上出现的点的个数,把每个名字的所有节点按dfs序排列,加上每个点到根的距离,但是要减掉多算的(这个点到这个点和上一个点的lca的链上的个数)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define N 400005 #define len 1000000 #define up 20 using namespace std; struct uu{ int l,r,p; bool operator<(const uu&x)const{return r<x.r;} }qer[N]; int ai[N],bi[N],ans[N]={0},point[N]={0},next[N],en[N],dt[N][2]={0},tt=0,tot=0,po[N],gi[N], cc[N],eg[N][2],fa[N][up],dep[N]={0},f[N],sz,val[N],que[len+1],po1[N]={0},ne1[N], la[N]={0},en1[N]; map<int,int> ch[N]; map<int,int>::iterator it; map<int,int>::iterator ed; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} int lowbit(int x){return x&(-x);} void ins(int x,int y){for (;x<N;x+=lowbit(x)) cc[x]+=y;} int ask(int x){int sum=0;for (;x;x-=lowbit(x)) sum+=cc[x];return sum;} int cmp(int x,int y){return dt[x][0]<dt[y][0];} int lca(int x,int y){ int i; if (dep[x]<dep[y]) swap(x,y); for (i=up-1;i>=0;--i) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if (x==y) return x; for (i=up-1;i>=0;--i) if (fa[x][i]!=fa[y][i]){ x=fa[x][i];y=fa[y][i]; }return fa[x][0];} struct use{ void insert(int l,int x,int kk){ int i,u=0,v; for (i=1;i<=l;++i){ v=ch[u][ai[i]]; if (!v){ v=ch[u][ai[i]]=++sz;val[sz]=0; }u=v; if (!kk){ne1[++tot]=po1[u];po1[u]=tot;en1[tot]=x;} }val[u]=1;if (kk) po[x]=u; } void pre(){ int head,tail,u,v,r; head=tail=0; ed=ch[0].end(); for (it=ch[0].begin();it!=ed;++it){ que[++tail]=it->second; f[it->second]=0;add(0,it->second); }while(head!=tail){ r=que[++head]; ed=ch[r].end(); for (it=ch[r].begin();it!=ed;++it){ u=que[++tail]=it->second;v=f[r]; while(v && !ch[v][it->first]) v=f[v]; f[u]=ch[v][it->first];add(f[u],u); } } } void dfs(int u,int ff){ int i;dt[u][0]=++tt;gi[tt]=u; fa[u][0]=ff;dep[u]=dep[ff]+1; for (i=1;i<up;++i) fa[u][i]=fa[fa[u][i-1]][i-1]; for (i=point[u];i;i=next[i]) dfs(en[i],u); dt[u][1]=tt; } void find(int x){ int i,u=0;que[0]=0; for (i=eg[x][0];i<=eg[x][1];++i){ u=ch[u][bi[i]]; que[++que[0]]=u; }sort(que+1,que+que[0]+1,cmp); } }ac; int main(){ int n,m,i,j,k,l,nt;scanf("%d %d",&n,&m); for (tot=nt=0,i=1;i<=n;++i){ scanf("%d",&k);l=k; for (j=1;j<=k;++j){ scanf("%d",&ai[j]);bi[j+nt]=ai[j]; }scanf("%d",&k);ai[++l]=20000;bi[l+nt]=20000; for (j=1;j<=k;++j){ scanf("%d",&ai[j+l]);bi[j+l+nt]=ai[j+l]; }l+=k;ac.insert(l,i,0); eg[i][0]=nt+1;eg[i][1]=nt=nt+l; }for (i=1;i<=m;++i){ scanf("%d",&k);l=k; for (j=1;j<=k;++j) scanf("%d",&ai[j]); ac.insert(l,i,1); }tot=0;ac.pre();ac.dfs(0,0); memset(cc,0,sizeof(cc)); for (i=1;i<=m;++i) qer[i]=(uu){dt[po[i]][0],dt[po[i]][1],i}; sort(qer+1,qer+m+1); for (l=i=1;i<=tt;++i){ for (j=po1[gi[i]];j;j=ne1[j]){ if (la[en1[j]]) ins(la[en1[j]],-1); ins(i,1);la[en1[j]]=i; }for (;l<=m&&qer[l].r==i;++l) ans[qer[l].p]=ask(qer[l].r)-ask(qer[l].l-1); }for (i=1;i<=m;++i) printf("%d\n",ans[i]); memset(cc,0,sizeof(cc)); for (i=1;i<=m;++i){ ins(dt[po[i]][0],1); ins(dt[po[i]][1]+1,-1); }for (i=1;i<=n;++i){ ac.find(i);nt=0; for (j=1;j<=que[0];++j){ nt+=ask(dt[que[j]][0]); if (j>1){ l=lca(que[j-1],que[j]); nt-=ask(dt[l][0]); } }printf("%d",nt); if (i<n) printf(" "); else printf("\n"); } }