CodeForces 204E Little Elephant and Strings
http://codeforces.com/contest/204/problem/E
输入n个字符串,由小写字母组成。
对于每一个字符串,求出这个字符串,有多少个区间[L,R],使得子串L..R在至少K个字符串中出现,包括它本身。
SAM竟有如此美妙的构造方法。
首先用一个Trie来构造SAM。然后考虑par树,我们要求的是每个结点所代表的子串,包含了多少个字符串。
如果沿着Trie上的结点对应的SAM结点来访问SAM的话,可以发现对于每个结点它以及它沿着par树上的结点都在Trie上出现过一次,将这个结点的访问次数加一,那么对par树用一遍dfs使cnt[u]+=cnt[v]似乎就可以求出所有结点的访问次数了。但是这样会有一个重复计数,两个结点的公共祖先以上的结点重复计数过了,因此把它们的公共结点访问次数减一就对了。用Tarjan做一遍离线的LCA可以解决,记录Trie对应的每个串所包含的点的当前的公共祖先,如果有一个新的结点出现,就处理旧的公共祖先与当前点的公共祖先。
最后保留下所有出现次数>=k的结点。统计这个结点的len[u]-len[par[u]]即包含的子串数。我们通过Trie知道了某个结点是否是某个串的前缀,那么这个结点的后缀也应该在这个串上,统计par树上的父结点到当前结点的cnt,累加到该串的答案中。
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <vector> 5 #include <queue> 6 7 using namespace std; 8 typedef long long LL; 9 typedef vector<int> VI; 10 typedef vector<int>::iterator Iter; 11 const int maxn=210000; 12 const int MAXS=26; 13 char buf[maxn]; 14 LL ans[maxn]; 15 int TtoM[maxn],MtoT[maxn]; 16 int n,K; 17 int cnt[maxn]; 18 namespace Trie{ 19 int trans[maxn][26]; 20 VI spb[maxn]; 21 int tot; 22 int NewNode(){return tot++;} 23 void Insert(char s[],int id){ 24 int u=0; 25 for (int i=0;s[i];i++){ 26 int c=s[i]-'a'; 27 if (!trans[u][c]) trans[u][c]=NewNode(); 28 u=trans[u][c]; 29 spb[u].push_back(id); 30 } 31 } 32 void Init(){ 33 tot=0; 34 NewNode(); 35 } 36 } 37 38 namespace SAM{ 39 int trans[maxn][26],par[maxn],len[maxn],tot; 40 VI adj[maxn]; 41 inline int NewNode(){ 42 memset(trans[tot],0,sizeof(trans[tot])); 43 return tot++; 44 } 45 inline int NewNode(int u){ 46 memcpy(trans[tot],trans[u],sizeof(trans[u])); 47 par[tot]=par[u]; 48 return tot++; 49 } 50 inline int h(int u){ 51 return len[u]-len[par[u]]; 52 } 53 int Extend(int last,int w){ 54 int p=last,np=NewNode(); 55 len[np]=len[p]+1; 56 while (p&&!trans[p][w]){ 57 trans[p][w]=np; 58 p=par[p]; 59 } 60 if (!p&&!trans[p][w]) { 61 trans[p][w]=np; 62 par[np]=0; 63 } 64 else{ 65 int q=trans[p][w]; 66 if (len[p]+1==len[q]) par[np]=q; 67 else{ 68 int nq=NewNode(q); 69 len[nq]=len[p]+1; 70 par[q]=par[np]=nq; 71 while (p&&trans[p][w]==q){ 72 trans[p][w]=nq; 73 p=par[p]; 74 } 75 if (!p&&trans[p][w]==q) trans[p][w]=nq; 76 } 77 } 78 return np; 79 } 80 void Gao(){ 81 tot=0; 82 queue<int>que; 83 que.push(0); 84 TtoM[0]=NewNode(); 85 while (que.size()){ 86 int u=que.front(); 87 que.pop(); 88 for (int w=0;w<26;w++){ 89 int v=Trie::trans[u][w]; 90 if (v){ 91 TtoM[v]=Extend(TtoM[u],w); 92 MtoT[TtoM[v]]=v; 93 que.push(v); 94 } 95 } 96 } 97 for (int i=1;i<tot;i++) adj[par[i]].push_back(i); 98 } 99 void Check(){ 100 for (int u=1;u<tot;u++){ 101 cnt[u]=cnt[u]>=K?h(u):0; 102 } 103 } 104 } 105 106 namespace Tarjan{ 107 int pa[maxn],r[maxn],a[maxn],l[maxn]; 108 void Make(int x){ 109 r[x] = 0; 110 pa[x] = x; 111 a[x] = x; 112 } 113 int Find(int x){ 114 if (x!=pa[x]) pa[x]=Find(pa[x]); 115 return pa[x]; 116 } 117 void Union(int _x, int _y){ 118 int x=Find(_x); 119 int y=Find(_y); 120 if (r[x]<r[y]) pa[y]=x; 121 else{ 122 pa[x]=y; 123 a[y]=_x; 124 if (r[x]==r[y]) r[x]++; 125 } 126 } 127 void Tarjan(int u=0){ 128 Make(u); 129 for (Iter it=SAM::adj[u].begin();it!=SAM::adj[u].end();it++){ 130 Tarjan(*it); 131 Union(u,*it); 132 } 133 if (MtoT[u]){ 134 for (Iter it=Trie::spb[MtoT[u]].begin();it!=Trie::spb[MtoT[u]].end();it++){ 135 int v=*it; 136 --cnt[a[Find(l[v])]]; 137 ++cnt[u]; 138 l[v]=u; 139 140 } 141 } 142 } 143 void dfs1(int u=0){ 144 int sz=SAM::adj[u].size(); 145 for (int i=0;i<sz;i++){ 146 int v=SAM::adj[u][i]; 147 dfs1(v); 148 cnt[u]+=cnt[v]; 149 } 150 } 151 void dfs2(int u=0){ 152 int sz=SAM::adj[u].size(); 153 for (int i=0;i<sz;i++){ 154 int v=SAM::adj[u][i]; 155 cnt[v]+=cnt[u]; 156 dfs2(v); 157 } 158 sz=Trie::spb[MtoT[u]].size(); 159 for (int i=0;i<sz;i++){ 160 int v=Trie::spb[MtoT[u]][i]; 161 ans[v]+=cnt[u]; 162 } 163 } 164 void Init(){ 165 memset(l,0,sizeof(l)); 166 } 167 } 168 int main() 169 { 170 scanf("%d%d",&n,&K); 171 Trie::Init(); 172 for (int i=0;i<n;i++){ 173 scanf("%s",buf); 174 Trie::Insert(buf,i); 175 } 176 SAM::Gao(); 177 Tarjan::Init(); 178 Tarjan::Tarjan(); 179 Tarjan::dfs1(); 180 SAM::Check(); 181 Tarjan::dfs2(); 182 for (int i=0;i<n;i++) printf("%I64d ",ans[i]);puts(""); 183 return 0; 184 }
CodeForces 452E Three strings
http://codeforces.com/contest/452/problem/E
有三个字符串s1、s2、s3,对于每一个l(1<=l<=min(|s1|,|s2|,|s3|)),问存在多少对(i1,i2,i3),使sk[ik... ik + l - 1](k = 1, 2, 3)相等,结果对109 + 7取模。
将三个串用特殊符号隔开拼接在一起,关键点在于求出结点在三个串上的right集合。
可以从根结点进行一个Dp来求出三个right集合。
而有了right集合后,对于一个结点u代表的子串来说,它对于len[u]存在right[0]*right[1]*right[2]个三元对,使子串相等。
注意如过一个状态表示的串出现了T次,那么该串的后缀也至少出现了T次,为了统计方便将num[len[par[u]]]减去num[len[u]],最后将num[i+1]累加到num[i]上即可。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 using namespace std; 7 const int MOD=1e9+7; 8 const int maxn=611111; 9 typedef long long LL; 10 LL num[maxn]; 11 namespace SAM{ 12 int trans[maxn][30],par[maxn],len[maxn],tot,last; 13 LL cnt[maxn][3]; 14 inline int NewNode(){ 15 memset(trans[tot],0,sizeof(trans[tot])); 16 return tot++; 17 } 18 inline int NewNode(int u){ 19 memcpy(trans[tot],trans[u],sizeof(trans[u])); 20 par[tot]=par[u]; 21 return tot++; 22 } 23 inline int h(int u){ 24 return len[u]-len[par[u]]; 25 } 26 void Extend(int w){ 27 int p=last,np=NewNode(); 28 len[np]=len[p]+1; 29 while (p&&!trans[p][w]){ 30 trans[p][w]=np; 31 p=par[p]; 32 } 33 if (!p&&!trans[p][w]) { 34 trans[p][w]=np; 35 par[np]=0; 36 } 37 else{ 38 int q=trans[p][w]; 39 if (len[p]+1==len[q]) par[np]=q; 40 else{ 41 int nq=NewNode(q); 42 len[nq]=len[p]+1; 43 par[q]=par[np]=nq; 44 while (p&&trans[p][w]==q){ 45 trans[p][w]=nq; 46 p=par[p]; 47 } 48 if (!p&&trans[p][w]==q) trans[p][w]=nq; 49 } 50 } 51 last=np; 52 } 53 bool vis[maxn]; 54 void Dp(int u){ 55 if (vis[u]) return; 56 vis[u]=true; 57 for (int k=0;k<3;k++){ 58 if (trans[u][26+k]) cnt[u][k]=1; 59 } 60 for (int i=0;i<26;i++){ 61 int v=trans[u][i]; 62 if (v){ 63 Dp(v); 64 for (int k=0;k<3;k++){ 65 cnt[u][k]=(cnt[u][k]+cnt[v][k])%MOD; 66 } 67 } 68 } 69 } 70 void Gao(){ 71 for (int i=1;i<tot;i++){ 72 LL tmp=((cnt[i][0]*cnt[i][1])%MOD*cnt[i][2])%MOD; 73 num[len[i]]=(num[len[i]]+tmp)%MOD; 74 num[len[par[i]]]=(num[len[par[i]]]-tmp+MOD)%MOD; 75 } 76 for (int i=tot-2;i>=1;i--){ 77 num[i]=(num[i]+num[i+1])%MOD; 78 } 79 } 80 void Init(){ 81 memset(vis,0,sizeof(vis)); 82 memset(cnt,0,sizeof(cnt)); 83 tot=0; 84 NewNode(); 85 } 86 void Debug(int tp=0){ 87 if (tp==0) 88 for (int i=0;i<tot;i++){ 89 printf("i=%d,par=%d,len=%d\n",i,par[i],len[i]); 90 for (int j=0;j<30;j++){ 91 if (trans[i][j]){ 92 printf("to %d %c\n",trans[i][j],(char)(j+'a')); 93 } 94 } 95 } 96 if (tp==1) 97 for (int i=0;i<tot;i++){ 98 printf("i=%d %I64d %I64d %I64d\n",i,cnt[i][0],cnt[i][1],cnt[i][2]); 99 } 100 } 101 } 102 const int INF=0x3f3f3f3f; 103 char s[maxn]; 104 int main() 105 { 106 SAM::Init(); 107 int n=INF; 108 scanf("%s",s); 109 n=min(n,(int)strlen(s)); 110 for (int i=0;s[i];i++) SAM::Extend(s[i]-'a'); 111 SAM::Extend(26); 112 scanf("%s",s); 113 n=min(n,(int)strlen(s)); 114 for (int i=0;s[i];i++) SAM::Extend(s[i]-'a'); 115 SAM::Extend(27); 116 scanf("%s",s); 117 n=min(n,(int)strlen(s)); 118 for (int i=0;s[i];i++) SAM::Extend(s[i]-'a'); 119 SAM::Extend(28); 120 SAM::Dp(0); 121 //SAM::Debug(0); 122 //SAM::Debug(1); 123 SAM::Gao(); 124 for (int i=1;i<=n;i++){ 125 if (i>1) printf(" "); 126 printf("%I64d",num[i]); 127 } 128 puts(""); 129 return 0; 130 }
CodeForces 316G3 Good Substrings
http://codeforces.com/contest/316/problem/G3
统计一个字符串有多少个好的子串,好的子串是指,有n个规则(P,L,R),其中P是一个字符串,L与R是一个整数,如果串T在串P中出现的次数在[L,R]区间就称T满足规则。而满足所有规则的子串称为好的子串。
HDU 4943 K-th good string
http://acm.hdu.edu.cn/showproblem.php?pid=4943
有一个字符串S,定义(x,p)为S[p-x+1,p-x+2,…,p]。
有三种类型的信息:
(x,p) 是好的,这里不包含(x,p)的子串。
(x,p) 是不好的。
(x,p)和整数k,回答第k个好串的长度。
ACdream 1117 Suffix Automation
http://acdream.info/problem?pid=1117
给你一个字符串集合(包含若干个字符串),最多10w次询问。
每次询问 i 串的a后缀跟 j 串的 b 后缀的最长的"特殊"公共前缀,这个公共前缀需要在字符串集合中出现。