https://ac.nowcoder.com/acm/contest/625/K
题意:
给出Q 个询问 i , 求 s[0..i-1] 与 s[i...len-1] 有多少相同的字串
分析:
给出了查询 , 容易想到先预处理出答案好吧 , 字符串的问题也容易想到后缀自动机 ,但是我们该怎么使用呢?
下面提供我的思路;
我们建立出SAM后 , 跑一边拓扑排序 ,根据SAM跑出来的拓扑排序的序列特性 , 我们可以求出 在当前状态st 的最大串字符出现的个数
for (int i = now; i >= 1; --i) {///得到的是最大字符串的出现次数 int x = rank[i]; endpos[slink[x]] += endpos[x]; }
可是这次我们需要求的是相同的串有多少 , 我们不可以暴力出SAM里面存有的串的个数 , 现在就来搞一个很奇妙的东西,
我们可以根据上面求出来的endpos , 去推出 有多少相同的字符串;
for(int i=1 ; i<=now; i++)///得到全部串的出现次数 { int x=rank[i]; ///到x这个状态时 , 有多少的后缀总共串 sum[x] = sum[slink[x]] + endpos[x]*(maxlen[x] - maxlen[slink[x]]); //cout<<sum[x]<<endl; }
我们知道 对于 now1 , 与now2=slink[now1] , 如果now1状态出现了 , 那么now2 的状态也肯定会出现 , 因为silnk 是链接now1接下去的后缀 ,也就是说now2 是now1的后缀
所以我们求当前now 有多少串相同的时候 , 就要+上一个的后缀价值 sum[x] = sum[slink[x]】 + 当前的价值
当前的价值又是 怎么计算呢?
我们知道 maxlen[x] - maxlen[slink[x]] 是表示当前的状态x 里面有多少的串 , 那这个状态出现的次数与包含的串相乘 , 不就是当前我们需要求的价值了吗
上面可能说的比较乱 , 主要是我巨菜不知如何表达鸭
上面是用str1 串去构建的SAM , 然后用str2 在这个自动机里面跑 , 与求LCA 很相似
可以参考https://www.cnblogs.com/shuaihui520/p/10686862.html
#include <bits/stdc++.h> #define LL long long #define P pair<int, int> #define lowbit(x) (x & -x) #define mem(a, b) memset(a, b, sizeof(a)) #define rep(i, a, n) for (int i = a; i <= n; ++i) #define mid ((l + r) >> 1) #define lc rt<<1 #define rc rt<<1|1 #define ll long long using namespace std; const int maxn = 1005; struct SAM{ int trans[maxn<<1][26], slink[maxn<<1], maxlen[maxn<<1]; // 用来求endpos int indegree[maxn<<1], endpos[maxn<<1], rank[maxn<<1], ans[maxn<<1]; // 计算所有子串的和(0-9表示) LL sum[maxn<<1],D[maxn]; int last, now, root; inline void newnode (int v) { maxlen[++now] = v; mem(trans[now],0); } inline void extend(int c) { newnode(maxlen[last] + 1); int p = last, np = now; // 更新trans while (p && !trans[p][c]) { trans[p][c] = np; p = slink[p]; } if (!p) slink[np] = root; else { int q = trans[p][c]; if (maxlen[p] + 1 != maxlen[q]) { // 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q] newnode(maxlen[p] + 1); int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q])); slink[nq] = slink[q]; slink[q] = slink[np] = nq; while (p && trans[p][c] == q) { trans[p][c] = nq; p = slink[p]; } }else slink[np] = q; } last = np; // 初始状态为可接受状态 endpos[np] = 1; } inline void init() { root = last = now = 1; slink[root]=0; mem(trans[root],0); mem(endpos,0); mem(sum,0); mem(indegree,0); mem(rank,0); } inline void getEndpos() { // topsort for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数 for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1]; // 统计度数小于等于 i 的节点的总数 for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后 // 从下往上按照slik更新 for (int i = now; i >= 1; --i) {///得到的是最大字符串的出现次数 int x = rank[i]; endpos[slink[x]] += endpos[x]; } for(int i=1 ; i<=now; i++)///得到全部串的出现次数 { int x=rank[i]; ///到x这个状态时 , 有多少的后缀总共串 sum[x] = sum[slink[x]] + endpos[x]*(maxlen[x] - maxlen[slink[x]]); //cout<<sum[x]<<endl; } } ///用一个串去跑的自动机 inline void work(string s,int W) { getEndpos(); int len=s.size(); int now=root; int t1=0; ll ret=0; for(int i=0 ; i<len ; i++) { int nowid=s[i]-‘a‘; if(trans[now][nowid])///这个状态有了 , 去下一个状态找 { t1++; now=trans[now][nowid]; //ret+=sum[slink[now]] + endpos[now]*(t1-maxlen[slink[now]]); } else { while(now!=0 && trans[now][nowid]==0) {now=slink[now];}///缩小范围找满足条件的 if(now) { t1 = maxlen[now]+1; now=trans[now][nowid]; } else { t1=0;now=root; } } ret+=sum[slink[now]] + endpos[now]*(t1-maxlen[slink[now]]); } D[W]=ret; //return ret; } }sam; int main() { string T;cin>>T; int len=T.size(); for(int i=1 ; i<len ; i++) { string t2; sam.init(); for(int j=0 ; j<i ; j++) { sam.extend(T[j]-‘a‘); } for(int j=i ; j<len ; j++) { t2+=T[j]; } sam.work(t2,i); } int E;scanf("%d",&E); while(E--) { int x; scanf("%d",&x); printf("%lld\n",sam.D[x]); } //- sam.all(); }
原文地址:https://www.cnblogs.com/shuaihui520/p/10713336.html