正题

题目链接:https://www.luogu.com.cn/problem/P7046


题目大意

给出一个长度为 \(n\) 的字符串,然后 \(m\) 次把它的一个子串加入集合。如果一个字符串在这个集合中作为字符串的后缀出现次数大于 \(k\) 那么这个字符串就会被计入贡献。

每次求计入贡献的字符串数和最长长度。

\(1\leq n,m\leq 5\times 10^5,0\leq k<n\)。


解题思路

考虑在parents树上如果能定位到一个节点的字符串那么它的后缀就是它到根的路径。

但是可能定位不到根,一种暴力的做法是每条边上建一个线段树然后暴力改。但是这样很麻烦可以考虑让每个询问一定能定位到一个节点。

我们直接建好树然后每次把询问倍增挂到对应的边上用set储存,然后再重新建一棵包含每个节点的树。

那么现在问题就变为了统计子树权值大于 \(k\) 的节点了,因为每个点到根的路径上满足条件的边一定是一段后缀,而每个节点最多统计一次,所以我们直接每次倍增找到最上面的没有统计的节点用树状数组+\(dfs\) 序判断是否合法就好了。

时间复杂度:\(O(n\log n)\)(默认 \(n,m\) 同级)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#define lowbit(x) (x&-x)
using namespace std;
const int M=1e6+10,N=2e6+10,T=21;
struct node{
int to,next;
}a[N];
int n,m,k,last,cnt,tot,ans2;long long ans1;
int ch[M][26],len[N],fa[N],L[M],R[M],p[M],las[M];
int ls[N],v[N],pos[N],f[N][T],rfn[N],ed[N],dos[M];
set<int> ct[M];vector<int> q[M];char s[M];
set<int>::iterator it;
void Insert(int c){
int p=last,np=last=++cnt;len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1)fa[np]=q;
else{
int nq=++cnt;len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
}
return;
}
void work(int p,int l,int r){
int x=pos[r];
for(int i=T-1;i>=0;i--)
if(len[f[x][i]]>=r-l+1)x=f[x][i];
if(ct[x].count(r-l+1))return;
ct[x].insert(r-l+1);q[x].push_back(p);
return;
}
void addl(int x,int y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;return;
}
bool cmp(int x,int y)
{return R[x]-L[x]+1<R[y]-L[y]+1;}
bool cMp(int x,int y)
{return len[x]<len[y];}
void dfs(int x){
rfn[x]=++cnt;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
f[y][0]=x;dfs(a[i].to);
}
ed[x]=cnt;return;
}
struct TreeBinary{
int t[N];
void Change(int x,int val){
while(x<=n){
t[x]+=val;
x+=lowbit(x);
}
return;
}
int Ask(int x){
int ans=0;
while(x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
}B;
int main()
{
scanf("%d%d%d",&n,&m,&k);
scanf("%s",s+1);cnt=last=1;
for(int i=1;i<=n;i++)Insert(s[i]-'a'),pos[i]=last;
for(int i=1;i<=cnt;i++)f[i][0]=fa[i];
for(int j=1;j<T;j++)
for(int i=1;i<=cnt;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=1;i<=m;i++){
scanf("%d%d",&L[i],&R[i]);
work(i,L[i],R[i]);
}
for(int i=1;i<=cnt;i++)p[i]=i,ct[i].insert(len[i]);
sort(p+1,p+1+cnt,cMp);
int pnt=cnt;cnt=1;las[1]=1;
for(int i=2;i<=pnt;i++){
int x=p[i];
sort(q[x].begin(),q[x].end(),cmp);
int z=0;las[x]=las[fa[x]];
it=ct[x].begin();
while(1){
++cnt;addl(las[x],cnt);las[x]=cnt;
int W=*it;len[cnt]=*it;
while(z<q[x].size()&&R[q[x][z]]-L[q[x][z]]+1<=W)
dos[q[x][z]]=cnt,z++;
it++;if(it==ct[x].end())break;
}
}
n=cnt;cnt=0;dfs(1);v[0]=1;len[0]=-1;
for(int j=1;j<T;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=1;i<=m;i++){
int p=dos[i];
if(!p){printf("%lld %d\n",ans1,ans2);continue;}
B.Change(rfn[p],1);
if(v[p]){printf("%lld %d\n",ans1,ans2);continue;}
while(!v[p]){
int x=p;
for(int j=T-1;j>=0;j--)
if(!v[f[x][j]])x=f[x][j];
int w=B.Ask(ed[x])-B.Ask(rfn[x]-1);
if(w>k){
v[x]=1;
ans1+=len[x]-len[f[x][0]];
ans2=max(ans2,len[x]);
}
else break;
}
printf("%lld %d\n",ans1,ans2);
}
return 0;
}

P7046-「MCOI-03」诗韵【SAM,倍增,树状数组】的更多相关文章

  1. 【bzoj3488】[ONTAK2010]Highways DFS序+树上倍增+树状数组

    题目描述 一棵n个点的树,给定m条路径,q次询问包含一条路径的给定路径的个数+1 输入 The first line of input contains a single integer N(1< ...

  2. loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增

    题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...

  3. LOJ #2719. 「NOI2018」冒泡排序(组合数 + 树状数组)

    题意 给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列满足 字典序比 \(p\) 大 : 它进行冒泡排序所需要交换的次数可以取到下界,也就是令第 \(i\) 个数为 \(a_i\ ...

  4. 「模拟赛20180307」三元组 exclaim 枚举+树状数组

    题目描述 给定 \(n,k\) ,求有多少个三元组 \((a,b,c)\) 满足 \(1≤a≤b≤c≤n\)且\(a + b^2 ≡ c^3\ (mod\ k)\). 输入 多组数据,第一行数据组数\ ...

  5. LG5200 「USACO2019JAN」Sleepy Cow Sorting 树状数组

    \(\mathrm{Sleepy Cow Sorting}\) 问题描述 LG5200 题解 树状数组. 设\(c[i]\)代表\([1,i]\)中归位数. 显然最终的目的是将整个序列排序为一个上升序 ...

  6. LOJ6041 SAM+set+树状数组

    首先对于原串建$SAM$,我们可以发先在一个点$i$的$right$集合里的点的相似度就是$len[i]$,于是可以将$SAM$的$right$集合通过$set$来启发式合并,每次加入新的点对$(i, ...

  7. 「ZJOI2017」树状数组(二维线段树)

    「ZJOI2017」树状数组(二维线段树) 吉老师的题目真是难想... 代码中求的是 \(\sum_{i=l-1}^{r-1}a_i\),而实际求的是 \(\sum_{i=l}^{r}a_i\),所以 ...

  8. loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分

    $ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinob ...

  9. 「UNR#1」奇怪的线段树

    「UNR#1」奇怪的线段树 一道好题,感觉解法非常自然. 首先我们只需要考虑一次染色最下面被包含的那些区间,因为把无解判掉以后只要染了一个节点,它的祖先也一定被染了.然后发现一次染色最下面的那些区间一 ...

随机推荐

  1. SpringBoot快速入门(二)

    2.SpringBoot原理分析 2.1.SpringBoot自动配置 Condition Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Be ...

  2. npm 基本命令的使用

    1.npm -v 查看npm版本 2.使用npm命令安装模块 npm install Module Name 例如安装jquery模块 npm install jquery 3.卸载模块 npm un ...

  3. open62541(opcua)传输延迟探索小记

    缘起 将open62541作为中间件使用代替自定义数据的RPC,client通过订阅valueChange来接收数据.使用时发现有一些问题: 前后两次产生的数据相同时,不会触发valueChange ...

  4. mzy,struts学习(一)

    大家都在讲struts已经过时了,现在都是前后台分离,没有必要去学一个淘汰的框架,但是怎么讲呢?我觉得,struts能够流行那么多年,肯定有它的原因,肯定有很多优秀和好的地方,有一个指导过我的人给我讲 ...

  5. Spring系列之集成Druid连接池及监控配置

    前言 前一篇文章我们熟悉了HikariCP连接池,也了解到它的性能很高,今天我们讲一下另一款比较受欢迎的连接池:Druid,这是阿里开源的一款数据库连接池,它官网上声称:为监控而生!他可以实现页面监控 ...

  6. Python也可以拥有延迟函数

    延迟函数defer 我们知道在Golang中有一个关键字defer,用它来声明在函数调用前,会让函数*延迟**到外部函数退出时再执行,注意,这里的退出含义:函数return返回或者函数panic退出 ...

  7. APP 兼容性测试之云测平台体验

    前言 兼容性测试主要通过人工或自动化的方式,在需要覆盖的终端设备上进行功能用例执行,查看软件性能.稳定性等是否正常. 对于需要覆盖的终端设备,大型互联网公司,像BAT,基本都有自己的测试实验室,拥有大 ...

  8. Git使用教程六

    冲突的产生与解决 案例:模拟产生冲突. ①同事在下班之后修改了线上仓库的代码 注意:此时我本地仓库的内容与线上不一致的. 2.第二天上班的时候,我没有做git pull操作,而是直接修改了本地的对应文 ...

  9. golang GC 垃圾回收机制

    垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行. Golang中的垃圾回收主要应用三色标记法, ...

  10. Python - 面向对象编程 - 子类方法的重写

    继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 方法的重写 在子类继承父类时,子类会拥有父类的所有属性和方法 但当父类的方法实现不满足子类需要 ...