后缀自动机模板

1 . 求不同子串的种类

2.长度为k的字符串的个数

3.计算所有子串的和(0-9表示)(前导0与非前导0)

4. 一种是在字符串后面添加一个字符,另一个是查询出现过最少出现K次的字串个数。

5. 恰好出现k次的字符串,至少k次的子串-至少(k+1)次的子串 =至少k

题目来源

 1445

后缀自动机二·重复旋律5

Lv.42016-12-10534
 1449

后缀自动机三·重复旋律6

Lv.42016-12-17430
 1457

后缀自动机四·重复旋律7

Lv.12016-12-24324

     http://acm.hdu.edu.cn/showproblem.php?pid=4641

     http://acm.hdu.edu.cn/showproblem.php?pid=6194

 


#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))
const int maxn = 5500005;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
const LL mod = 1e9 + 7;
int len;
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];
    // res至少出现k次的子串个数
    LL sum[maxn<<1],res=0;
    int last, now, root;

    inline void newnode (int v) {
        maxlen[++now] = v;
        mem(trans[now],0);
    }
    //至少出现k次的子串个数,其他情况可以不需要
    inline void extend(int c,int k) {
        newnode(maxlen[last] + 1);
        int p = last, np = now;
        sum[now]=0;
        // 更新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]));
                 sum[nq]=sum[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;
        
        //查询至少出现k次子串个数
        int w=np;
        while(w && sum[w] < k )
        {
            sum[w]++;
            if(sum[w]==k) res+=maxlen[w]-maxlen[slink[w]];
            w=slink[w];
        }
        
    }
    inline void init()
    {
        root = last = now = 1;
        res=0;
        sum[root]=0;
        slink[root]=0;
        mem(trans[root],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];
        }
    }
    // 计算所有子串的和(0-9表示)(算前导0与不算前导0)
    inline LL getSum() {
        // 拓扑排序
        for (int i = 1; i <= now; ++i) indegree[ maxlen[i] ]++;
        for (int i = 1; i <= now; ++i) indegree[i] += indegree[i-1];
        for (int i = 1; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i;
        mem(endpos, 0);
        endpos[1] = 1; // 从根节点向后求有效的入度
        for (int i = 1; i <= now; ++i) {
            int x = rank[i];
            for (int j = 0; j < 10; ++j) {
                int nex = trans[x][j];
                if (!nex) continue;
                //if(maxlen[x]==0&&j==0) continue;//不算前导0
                endpos[nex] += endpos[x]; // 有效入度
                LL num = (sum[x] * 10 + endpos[x] * j) % mod;
                sum[nex] = (sum[nex] + num) % mod; // 状态转移
            }
        }
        LL ans = 0;
        for (int i = 2; i <= now; ++i) ans = (ans + sum[i]) % mod;
        return ans;
    }

    // 求不同的子串种类
    inline LL all () {
        LL ans = 0;
        for (int i = root+1; i <= now; ++i) {
            ans += maxlen[i] - maxlen[ slink[i] ];
        }
        return ans;
    }
    // 长度为K的字符串有多种,求出现次数最多的次数
    inline void get_Maxk() {
        getEndpos();
        for (int i = 1; i <= now; ++i) {
            ans[maxlen[i]] = max(ans[maxlen[i]], endpos[i]);
        }
        for (int i = len; i >= 1; --i) ans[i] = max(ans[i], ans[i+1]);
        for (int i = 1; i <= len; ++i) //cout << ans[i] << endl;
            printf("%d\n", ans[i]);
    }
     //求长度为为k的字符串有多少种
    void klensum()
    {
    /// ans[i] 得到的是长度为 i 不同字符串的个数
        getEndpos();

        for(int i=root+1;i<=now;i++)
        {
            ans[maxlen[i]]=max(ans[maxlen[i]], endpos[i]);
        }
        for(int i=len;i>=1;i--)
        {
            ans[i]=max(ans[i], ans[i+1]);

        }
        for(int i=1 ; i<=len ; i++)
            printf("%d\n",ans[i]);

    }  

}sam;

int main()
{


  int n,q,k;
    int t;scanf("%d",&t);
    while(t--)
    {

    string str;int k;
    cin>>k>>str;
    sam.init();
    n=str.size();
    for(int i=0 ; i<n ; i++)
    {
        sam.extend(str[i]-'a'  , k);
    }
    LL sum1=sam.res;
    sam.init();
    for(int i=0 ; i<n ; i++)
    {
        sam.extend(str[i]-'a'  , k+1);
    }
    LL sum2=sam.res;
    printf("%lld\n",sum1-sum2);
    }

}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        sam.init();
        for(int i=1 ; i<=n;  i++)
        {
            scanf("%s",str);
		    len=strlen(str);
            for(int j=0 ; j<len ; j++)
            {
             sam.extend(str[j]-'0',0);
            }
            sam.extend(10,0); //分割开字符串
        }
        printf("%d\n",sam.getSum());

    }
}    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值