2019年华南理工大学程序设计竞赛(春季赛) K Parco_Love_String(后缀自动机)找两个串的相同字串有多少

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

时间: 2024-09-30 22:49:22

2019年华南理工大学程序设计竞赛(春季赛) K Parco_Love_String(后缀自动机)找两个串的相同字串有多少的相关文章

C 六学家的困惑 【YY】 (2019年华南理工大学程序设计竞赛(春季赛))

冲鸭,去刷题:https://ac.nowcoder.com/acm/contest/625/C 小六喜欢两全其美的事情,今天就正好有一个这样的机会. 小六面前有两根管子,管子里面放满了数字为1到9的小球.每次取球时,小六会先选择一根管子,再从这根管子的某一侧(左侧或右侧)取出一个球.在满足取球规则的情况下,他可以任意顺序取出所有小球.假如小六依次取出的球的编号为a1,a2,?,ana1,a2,?,an,则他最后就得到了一个形如¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯a1a2?ana1a

2019年华南理工大学程序设计竞赛(春季赛)A NB群友

https://ac.nowcoder.com/acm/contest/625/A 题意:给出一个区间范围 , 求有多少个数的每一位的积是在这个区间里面的 分析:没错了 ,就是记忆化暴力搜索 ,不断的枚举除 i ,i是2到9 , 看是否合法 ,  对于枚举到相同的状态的时候 , 后面的价值肯定和上一个状态是一样的 , 所以这样就好拉 #include<bits/stdc++.h> using namespace std; #define ll long long map<ll , ll

2018华南理工大学程序设计竞赛 H-对称与反对称

H-对称与反对称 题目描述 给出一个N*N的方阵A.构造方阵B,C: 使得A = B + C.其中 B为对称矩阵,C为反对称矩阵. 对于方阵S中的任意元素,若(S)ij = (S)ji,则称S为对称矩阵 对于方阵T中的任意元素,若(T)ij = -(T)ji,则称T为反对称矩阵 注意,所有运算在模M意义下 输入描述: 输入包含多组数据,处理到文件结束每组数据,第一行包含两个正整数N,M(1 <= N <= 1000, 1 <= M <= 1000,000,001)分别表示方阵大小与

2017-5-19-Train:2017年浙江理工大学程序设计竞赛校赛

Problem A: 回文(签到题) Description 小王想知道一个字符串是否为ABA’型字符串.ABA’型字符串的定义:S=ABA’,A,B,A’都是原字符串的子串(不能是空串),A’的意思是A的反转串,B不一定要和A或A’不同.符合ABA’型的例如:"aba”,"acbbca”,"abcefgcba”等."Abcefgcba”是ABA’型,因为它能找到一组对应的A("abc”),B(”efg”)和A’("cba")满足定义.

2015年浙江理工大学程序设计竞赛

做了两题 Problem D: 逻辑运算 Description 还记得大学里学过的模电么,今天就让我们将与或非变成一道题吧. 给你一个与或非的表达式,求出这个表达式的值,表达式总共有八种字符. 三种逻辑运算符按照优先级排列如下. ‘!’:表示取反. ‘&’:逻辑与. ‘|’:逻辑或. 两个字符‘T’,‘F‘分别表示true和 false. 另外还有左右括号,空格三种字符.跟一般的表达式一样,括号可以改变优先级. Input 每组数据输入一行字符串,字符串长度小于等于100. Output 输出

埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛 B合约数

链接:https://www.nowcoder.com/acm/contest/91/B来源:牛客网牛客网没有账号的同学,请用这个注册,支持楼主,谢谢. 题目描述 给定一棵n个节点的树,并且根节点的编号为p,第i个节点有属性值vali, 定义F(i): 在以i为根的子树中,属性值是vali的合约数的节点个数.y 是 x 的合约数是指 y 是合数且 y 是 x 的约数.小埃想知道对1000000007取模后的结果. 输入描述: 输入测试组数T,每组数据,输入n+1行整数,第一行为n和p,1<=n<

2019年华南理工大学软件学院ACM集训队选拔赛 Round1

TIps: 1.所有代码中博主使用了scanf和printf作为输入输出  2.代码中使用了define LL long long 所以在声明变量的时候 LL其实就等价于long long 希望这两点不会成为读者看代码时候的障碍qwq 另外题目链接我会放在最后 如果需要请往下拖一拖 T1  这道题其实就是单纯对数据进行排序,但是因为关键字不止一种(解题数m和罚时t) 并且输出的是队伍的名字(也就是序号) 而正常排序完之后我们就会发现 我们并不知道当前各个位置的数对应的队伍名是什么了(也就是其原本

2019-2020"新生赛"长沙理工大学程序设计竞赛E题合法括号

题目描述 众所周知,佳爷是集训队最强,他经常喜欢鄙视集训队最菜的PC,这天他又来了,他丢给PC一道题目: 给你一个字符串,该字符串只包含符号 '(’ 和 ‘)', ,我们称那些左右括号可以一一对应的括号字符串为完美字符串, 比如"()()()",  "((()))", "((()))()()", 都是完美字符串 而"((())", "()(", "((()))()(" 不是完美字符串.

"新生赛"长沙理工大学程序设计竞赛

B.happy card 列出所有条件:人数 n,总的快乐牌 num,限制手牌 m 我们可以 DP 的是:前 i 个人在总牌数为 j 的情况下,第 i个人拿 k 张牌的最大幸福值.想想似乎和01背包有点儿类似,那么状态转移方程也就不难写出来了: dp?[i][j]=max?(dp?[i−1][j−k]+h[k],dp?[i-1][j]) 当然我们的第一维其实可以直接滚动掉的.. 其实算法还可以再进行时间的优化,将其化为 n^2 算法. 实际上人数是一个无用条件,我们知道总的快乐牌数,那么人数一定