Distinct Substrings(扩展KMP)

Distinct Substrings

写完这题发现自己曾经的扩展KMP板子( Z Z Z函数)太laji了!现在的板子简洁又漂亮,并且这题很妙!

题意

给定一个长为 n n n的数字串,问在尾部独立的添加 1 1 1~ m m m这些数字分别会使原串增加多少本质不同的子串

思路

  1. 看到本质不同,首先想到了后缀自动机,每次在结尾插入元素后只需要知道插入元素的父节点是谁,然后利用 l e n [ n p ] l e n [ f a [ n p ] ] len[np]-len[fa[np]] len[np]len[fa[np]]就可以知道增加了多少新的本质不同的子串
  2. 但就本题而言,有如下三点会否定这个做法:
    1. 空间 32 M 32M 32M,而后缀自动机要开很多的 1 e 6 1e6 1e6的数组
    2. 数组元素是数字,而不是字符,因此假设用 m a p map map存边,空间会爆炸!
    3. 多组数据+ O ( n l o g n ) O(nlogn) O(nlogn)+大常数在两秒内显然是跑不出来的
  3. 否定后缀自动机后,我们仍然可以利用这个求新增子串的思想;在后缀自动机中,思考一个节点的 f a t h e r father father节点是什么?答案是与当前节点 e n d p o s endpos endpos不同的最长的后缀,因此我们如果能够在不建立后缀自动机的情况下找到这个最长的后缀,那不就好了?
  4. 而正好, Z Z Z a l g o r i t h m algorithm algorithm刚好有一种经典的做法,考虑将原字符串翻转,就可以将求最长的后缀变成了求最长的前缀。并且要考虑分别添加 m m m个不同数字的情况下的子串增量,我们可以 O ( n ) O(n) O(n)的计算贡献,见如下式子:
    b e s t [ s [ i ] ] = m a x ( b e s t [ s [ i ] ] , 1 + z [ i + 1 ] ) best[s[i]]=max(best[s[i]],1+z[i+1]) best[s[i]]=max(best[s[i]],1+z[i+1])
    其中 b e s t [ p ] best[p] best[p]表示在翻转后的串的前面增加 p p p这个字符会有多少重复的子串出现,则 n + 1 b e s t [ p ] n+1-best[p] n+1best[p]就表示新增的本质不同的子串数量。这个 m a x max max旨在寻找最长的前缀
  5. 最后,此问题可以在 O ( T n ) O(T*n) O(Tn)的复杂度下解决,并且空间复杂度也完全 O K OK OK(不过这题数据好像有些问题,导致暴力 O ( n 2 ) (O(n^2)) O(n2) z z z数组也能过。。。)
#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}

const int maxn = 1e6+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;

int n, m;
int s[maxn], z[maxn], best[maxn];

int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    while(cin>>n>>m) {
        for(int i=1; i<=n; ++i) scanf("%d", &s[i]);
        reverse(s+1,s+1+n);
        for(int i=2, j=1; i<=n; ++i) {
            if(i<j+z[j]) z[i]=min(j+z[j]-i,z[i-j+1]);
            while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1]) z[i]++;
            if(i+z[i]>j+z[j]) j=i;
        }
        for(int i=1; i<=n; ++i) best[s[i]]=max(best[s[i]],1+z[i+1]);
        ll ans=0, three=1;
        for(int i=1; i<=m; ++i) {
            three=three*3%mod;
            ans^=three*(n+1-best[i])%mod;
        }
        cout<<ans<<endl;
        for(int i=1; i<=max(n,m); ++i) best[i]=z[i]=0;
    }
}
全部评论

相关推荐

想干测开的tomca...:让我来压力你!!!: 这份简历看着“技术词堆得满”,实则是“虚胖没干货”,槽点一抓一大把: 1. **项目描述是“技术名词报菜名”,没半分自己的实际价值** 不管是IntelliDoc还是人人探店,全是堆Redis、Elasticsearch、RAG这些时髦词,但你到底干了啥?“基于Redis Bitmap管理分片”是你写了核心逻辑还是只调用了API?“QPS提升至1500”是你独立压测优化的,还是团队成果你蹭着写?全程没“我负责XX模块”“解决了XX具体问题”,纯把技术文档里的术语扒下来凑字数,看着像“知道名词但没实际动手”的实习生抄的。 2. **短项目塞满超纲技术点,可信度直接***** IntelliDoc就干了5个月,又是RAG又是大模型流式响应又是RBAC权限,这堆活儿正经团队分工干都得小半年,你一个后端开发5个月能吃透这么多?明显是把能想到的技术全往里面塞,生怕别人知道你实际只做了个文件上传——这种“技术堆砌式造假”,面试官一眼就能看出水分。 3. **技能栏是“模糊词混子集合”,没半点硬核度** “熟悉HashMap底层”“了解JVM内存模型”——“熟悉”是能手写扩容逻辑?“了解”是能排查GC问题?全是模棱两可的词,既没对应项目里的实践,也没体现深度,等于白写;项目里用了Elasticsearch的KNN检索,技能栏里提都没提具体掌握程度,明显是“用过但不懂”的硬凑。 4. **教育背景和自我评价全是“无效信息垃圾”** GPA前10%这么好的牌,只列“Java程序设计”这种基础课,分布式、微服务这些后端核心课提都不提,白瞎了专业优势;自我评价那堆“积极认真、细心负责”,是从招聘网站抄的模板吧?没有任何和项目挂钩的具体事例,比如“解决过XX bug”“优化过XX性能”,纯废话,看完等于没看。 总结:这简历是“技术名词缝合怪+自我感动式凑数”,看着像“背了后端技术栈名词的应届生”,实则没干货、没重点、没可信度——面试官扫30秒就会丢一边,因为连“你能干嘛”都没说清楚。
点赞 评论 收藏
分享
链接
海梨花:我说话难听,你这简历跟没写没啥区别,搜搜别人的简历,用心写,不要随随便便就结束了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务