题目:https://loj.ac/problem/3055

先写了暴力。本来想的是 n<=300 的那个在树上暴力维护好整个字符串, x=1 的那个用主席树维护好字符串和 nxt 数组。但 x=1 的部分会 TLE ,而且似乎不太对的样子。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define pb push_back
#define ls Ls[cr]
#define rs Rs[cr]
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
int n;
namespace S1{
const int N=,M=N*N;
int fa[N],q[N],s[M],nxt[M]; ll sm[N];
vector<int> c[N],nt[N];
void add(int x,int y,int m=,int ch=)
{
sm[y]=sm[x]; fa[y]=x; if(!m)return;
int top=, cr=x;
while(cr)q[++top]=cr,cr=fa[cr];
int tot=;
for(int i=top;i;i--)
{
cr=q[i];
for(int j=,lm=c[cr].size();j<lm;j++)
s[++tot]=c[cr][j], nxt[tot]=nt[cr][j];
}
c[y].resize(m); nt[y].resize(m);
int i,j;
if(!tot){ s[]=c[y][]=ch;i=;j=;} else { i=tot+;j=;}
for(;j<=m;j++,i++)
{
s[i]=ch; cr=nxt[i-];
while(cr&&s[cr+]!=ch)cr=nxt[cr];
if(s[cr+]==ch)nxt[i]=cr+; else nxt[i]=;
c[y][j-]=ch; nt[y][j-]=nxt[i]; sm[y]+=nxt[i];
}
}
void solve()
{
int op,x; char ch[];
for(int i=;i<=n;i++)
{
op=rdn();x=rdn();
if(op==)
{ scanf("%s",ch); add(i-,i,x,ch[]-'a'+);}
else add(x,i);
printf("%lld\n",sm[i]);
}
}
}
namespace S2{
const int N=1e5+,M=2e6+;
int rt[N],tot,Ls[M],Rs[M],cd[N]; ll sm[N];
struct Node{ int c,nxt;}a[M];
int ins(int l,int r,int &cr,int pr,int p,int ch)
{
if(!cr){cr=++tot;ls=Ls[pr];rs=Rs[pr];}
if(l==r){a[cr].c=ch;return cr;}
int mid=l+r>>;
if(p<=mid)return ins(l,mid,ls,Ls[pr],p,ch);
return ins(mid+,r,rs,Rs[pr],p,ch);
}
Node qry(int l,int r,int cr,int p)
{
if(l==r)return a[cr]; int mid=l+r>>;
if(p<=mid)return qry(l,mid,ls,p);
return qry(mid+,r,rs,p);
}
void add(int cr,int pr,int m,int ch)
{
sm[cr]=sm[pr]; cd[cr]=cd[pr];
for(int i=,d;i<=m;i++)
{
cd[cr]++; d=ins(,n,rt[cr],rt[pr],cd[cr],ch);
int p=qry(,n,rt[cr],cd[cr]-).nxt;
while(p&&qry(,n,rt[cr],p+).c!=ch)
p=qry(,n,rt[cr],p).nxt;
if(p+!=cd[cr]&&qry(,n,rt[cr],p+).c==ch)//!=
a[d].nxt=p+;
else a[d].nxt=;
sm[cr]+=a[d].nxt;
}
}
void solve()
{
int op,x; char ch[];
for(int i=;i<=n;i++)
{
op=rdn();x=rdn();
if(op==)
{ scanf("%s",ch); add(i,i-,x,ch[]-'a'+);}
else {sm[i]=sm[x];rt[i]=rt[x];cd[i]=cd[x];}
printf("%lld\n",sm[i]);
}
}
}
int main()
{
n=rdn();
if(n<=){S1::solve();return ;}
if(n<=1e5){S2::solve();return ;}
return ;
}

然后看了题解。

因为有 “加入的字符和上一个不同” 的限制,所以考虑一段 x 的末尾后面能续上 x 的 nxt 数组,只有自己的 nxt 跳到了另一段 y 的末尾,满足 x 和 y 的字符与长度均相同。

那个 nxt 就是把一段看做一个字符、相同看做两段的字符与长度均相同的 nxt 数组。

一边跳 nxt 一边累计答案,方法是记录一个 lst 表示当前段已经有前 lst 个字符贡献过答案;如果遇到 c[ p+1 ] == c[ cr ] ( c[ ] 表示字符, p 表示跳到的 nxt ),那么能匹配上的是当前段的前 min( len[ p+1 ] , len[ cr ] ) 个字符(len 表示段长);其中之前没贡献过答案的就是本次要贡献答案的,贡献是 ( s[ p ] + lst + 1 ) 到 ( s[ p ] + min( len[ p+1 ] , len[ cr ] ) ) 的等差数列求和。然后把 lst 更新成 min( len[ p+1 ] , len[ cr ] ) 。

如果第一段的字符和自己相同,而第一段的长度比自己小(大于等于自己的话,在跳 nxt 的时候已经用等差数列加过了。所以跳 nxt 的 break 条件放在贡献答案之后),那么还可以给答案贡献 ( len[ cr ] - lst ) 倍的 len[ 1 ] 。(注意是 ( len[ cr ] - lst ) 而不是 ( len[ cr ] - len[ 1 ] ) )并且这种情况的 nxt[ cr ] 应该等于 1 而不是 0 。

把询问离线,在树上用全局变量维护当前的 c[ ] 和 nxt[ ] , dfs 一遍即可。这样复杂度不对,但可过。目前只写了这样。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
int Mn(int a,int b){return a<b?a:b;}
int Mx(int a,int b){return a>b?a:b;}
const int N=1e5+,mod=;
int upt(int x){while(x>=mod)x-=mod;while(x<)x+=mod;return x;} int n,c[N],len[N],tc[N],tl[N],s[N],nt[N];
int hd[N],xnt,to[N],nxt[N],ans[N];
int cz(int l,int r)
{
if(l>r)return ;
return (ll)(l+r)*(r-l+)/%mod;
}
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void dfs(int cr,int pr,int cd)
{
ans[cr]=pr;
if(len[cr])
{
cd++; nt[cd]=;/////
tc[cd]=c[cr]; tl[cd]=len[cr]; s[cd]=s[cd-]+len[cr];
if(cd==)
{
ans[cr]=cz(,len[cr]-); nt[cd]=;
}
else
{
int p=nt[cd-],lst=;
while()
{
if(tc[p+]==c[cr])
{
int tp=Mn(tl[p+],len[cr]);
if(tp>lst)
{ ans[cr]=upt(ans[cr]+cz(s[p]+lst+,s[p]+tp)); lst=tp;}
}
if(!p||(tc[p+]==c[cr]&&tl[p+]==len[cr]))break;
p=nt[p];
}
if(tc[p+]==c[cr]&&tl[p+]==len[cr])
nt[cd]=p+;
else if(!p&&tc[]==c[cr]&&tl[]<len[cr])
ans[cr]=(ans[cr]+(ll)tl[]*(len[cr]-lst))%mod,nt[cd]=;
//-lst not len[1]//nxt=1 not 0
}
}
for(int i=hd[cr];i;i=nxt[i])
dfs(to[i],ans[cr],cd);
}
int main()
{
n=rdn(); char ch[];
for(int i=;i<=n;i++)
{
int op=rdn();
if(op==){ int d=rdn();add(d,i);continue;}
len[i]=rdn(); scanf("%s",ch); c[i]=ch[]-'a'+;
add(i-,i);
}
dfs(,,);
for(int i=;i<=n;i++)printf("%d\n",ans[i]);
return ;
}

然后参考这里的题解(和代码):https://www.cnblogs.com/zhoushuyu/p/10680094.html

复杂度不对的原因是暴力跳 nxt 。可以建 “kmp自动机” ,就是 pr[ i ][ j ] 表示 i 位置后面接 j 字符的话 nxt 会跳到哪个位置。新的位置 i 继承它的 nxt 的 pr[ ][ ] ,i-1 的某个 pr[ ][ ] 值改为 i 。

根据接上来的长度不同,即使字符一样, nxt 仍可能跳到不同的位置。所以每个位置开 26 个主席树维护接上各种字符的不同长度, nxt 会跳到哪个位置。

边跳还要边统计答案。把这个信息也放到主席树上。

  答案由两部分构成。设当前段能匹配的长度为 len , 一部分答案是 1 ~ len 的等差数列求和,另一部分是 1 ~ len 对应的 nxt 位置的前缀长度求和。

  考虑已经做完当前段,让它给上一个位置的主席树一些更新。设当前段长为 cd , prs 表示到上一个位置为止的前缀段长。

  考虑原来的暴力,跳到一个字符相同的位置,可以给当前段的一个前缀的每个位置提供一种可能的贡献,这里需要把 1~cd 位置的 “可能贡献” 改成当前的 prs 。这样一定最优。

  所以把主席树上 1~cd 位置的值都改成 prs 。把 cd 位置的 nxt 改成当前段。求答案的时候,假设要匹配的段的长度是 cd2 ,那么它的 nxt 就是主席树 cd2 处记录的 nxt ,它的过程中答案就是主席树 1~cd2 位置的值的和。

注意处理与第一段匹配的情况。需要记录 “当前段最长能匹配多长” 。这个顺便记录即可。就是每次要修改的时候,对应值都可以对当前段长 cd 取 max 。

代码里 rt[ top ][ ch ] 表示 “通过 ch 的边进入 top 之后的种种可能” 。所以往下走的时候,是把 rt[ pr+1 ] 赋值给 rt[ top+1 ] ,用的就是 “通过当前字符从 pr 进入 pr+1 ” 的信息。(pr 表示当前位置的 nxt )

注意主席树新开节点的时候把原来的信息搬过来。

注意在外面枚举 0 点的出边,进入的时候把 rt[ 0 ][ ] 之类的改成初值。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define ls Ls[cr]
#define rs Rs[cr]
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=1e5+,M=5e6+,K=,mod=;
int upt(int x){while(x>=mod)x-=mod;while(x<)x+=mod;return x;} int n,m,cnt,hd[N],xnt,to[N],nxt[N],w[N],c[N],ps[N],ans[N];
int rt[N][K],mxl[N][K],prs[N],top,tc,tl;
int tot,Ls[M],Rs[M],sm[M],nt[M],tg[M],tim,dfn[M];
void add(int x,int y,int cd,int ch)
{to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=cd;c[xnt]=ch;}
int cal(int x){return (ll)(+x)*x/%mod;}
int nwnd(int cr)
{
if(dfn[cr]==tim)return cr; tot++; dfn[tot]=tim;
Ls[tot]=ls; Rs[tot]=rs;
sm[tot]=sm[cr]; nt[tot]=nt[cr]; tg[tot]=tg[cr];return tot;
}
void cz(int &cr,int len,int k){ cr=nwnd(cr); sm[cr]=(ll)len*k%mod;}
void pshd(int cr,int l,int mid,int r)
{
if(!tg[cr])return; int k=tg[cr]; tg[cr]=;
cz(ls,mid-l+,k); cz(rs,r-mid,k); tg[ls]=tg[rs]=k;
}
void mdfy(int l,int r,int &cr,int R,int k,int p)
{
if(!cr||dfn[cr]!=tim)cr=nwnd(cr);//
if(r<R){cz(cr,r-l+,k);tg[cr]=k;return;}
if(l==r){cz(cr,r-l+,k);nt[cr]=p;return;}
int mid=l+r>>; pshd(cr,l,mid,r);
mdfy(l,mid,ls,R,k,p);
if(mid<R)mdfy(mid+,r,rs,R,k,p);
sm[cr]=upt(sm[ls]+sm[rs]);
}
int qry(int l,int r,int cr,int R,int &p)
{
if(!cr)return ; if(r<R)return sm[cr];
if(l==r){p=nt[cr]; return sm[cr];}
int mid=l+r>>; pshd(cr,l,mid,r);
int ret=qry(l,mid,ls,R,p);
if(mid<R)ret=upt(ret+qry(mid+,r,rs,R,p));
return ret;
}
void dfs(int cr,int cd,int ch)
{
prs[++top]=prs[top-]+cd; int pr=;
if(top==)ans[cr]=upt(ans[cr]+cal(cd-)),tc=ch,tl=cd;
else
{
ans[cr]=upt(ans[cr]+qry(,m,rt[top][ch],cd,pr));
ans[cr]=upt(ans[cr]+cal(Mn(cd,mxl[top][ch])));//Mn!!
if(!pr&&tc==ch&&tl<cd)
{
if(cd>mxl[top][ch])
ans[cr]=(ans[cr]+(ll)tl*(cd-mxl[top][ch]))%mod;
pr=;///////
}
}
mxl[top][ch]=Mx(mxl[top][ch],cd);
tim++; mdfy(,m,rt[top][ch],cd,prs[top-],top);
for(int i=hd[cr];i;i=nxt[i])
{
memcpy(rt[top+],rt[pr+],sizeof rt[pr+]);//pr+1!!!
memcpy(mxl[top+],mxl[pr+],sizeof mxl[pr+]);
ans[to[i]]=ans[cr]; dfs(to[i],w[i],c[i]);
}
top--;
}
int main()
{
n=rdn(); char ch;
for(int i=;i<=n;i++)
{
int op=rdn(), x=rdn();
if(op==)
{
cin>>ch; ps[i]=++cnt; m=Mx(m,x);
add(ps[i-],ps[i],x,ch-'a'+);
}
else ps[i]=ps[x];
}
for(int i=hd[];i;i=nxt[i])
{
memset(rt[],,sizeof rt[]);/////
memset(mxl[],,sizeof mxl[]);
dfs(to[i],w[i],c[i]);
}
for(int i=;i<=n;i++)printf("%d\n",ans[ps[i]]);
return ;
}

LOJ 3055 「HNOI2019」JOJO—— kmp自动机+主席树的更多相关文章

  1. Loj #3055. 「HNOI2019」JOJO

    Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...

  2. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

  3. Loj #3056. 「HNOI2019」多边形

    Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...

  4. Loj 3058. 「HNOI2019」白兔之舞

    Loj 3058. 「HNOI2019」白兔之舞 题目描述 有一张顶点数为 \((L+1)\times n\) 的有向图.这张图的每个顶点由一个二元组 \((u,v)\) 表示 \((0\le u\l ...

  5. Loj #3057. 「HNOI2019」校园旅行

    Loj #3057. 「HNOI2019」校园旅行 某学校的每个建筑都有一个独特的编号.一天你在校园里无聊,决定在校园内随意地漫步. 你已经在校园里呆过一段时间,对校园内每个建筑的编号非常熟悉,于是你 ...

  6. [HNOI2019]JOJO(KMP自动机+主席树)

    一道神仙题,考察选手对KMP的深入理解. 先考虑没有2操作的做法.设每一段为一个二元组(x,c),考虑一段前缀匹配后缀,除了第一段的字符,其他段的二元组(x,c)必须相等,所以可以将其视为特殊字符进行 ...

  7. LOJ 3059 「HNOI2019」序列——贪心与前后缀的思路+线段树上二分

    题目:https://loj.ac/problem/3059 一段 A 选一个 B 的话, B 是这段 A 的平均值.因为 \( \sum (A_i-B)^2 = \sum A_i^2 - 2*B \ ...

  8. 【loj - 3055】「HNOI2019」JOJO

    目录 description solution accepted code details description JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或 ...

  9. LOJ 3057 「HNOI2019」校园旅行——BFS+图等价转化

    题目:https://loj.ac/problem/3057 想令 b[ i ][ j ] 表示两点是否可行,从可行的点对扩展.但不知道顺序,所以写了卡时间做数次 m2 迭代的算法,就是每次遍历所有不 ...

随机推荐

  1. 小波学习之一(单层一维离散小波变换DWT的Mallat算法C++和MATLAB实现) ---转载

      1 Mallat算法 离散序列的Mallat算法分解公式如下: 其中,H(n).G(n)分别表示所选取的小波函数对应的低通和高通滤波器的抽头系数序列. 从Mallat算法的分解原理可知,分解后的序 ...

  2. 自动化测试-21.RobotFrameWork配置安装

    更新pip python -m pip install --upgrade pip 1安装robotframework --pip install robotframework 2. 安装支持框架的运 ...

  3. ORACLE提示表名无效

    在创建ORACLE数据库时,创建表 提示表名无效 请查看数据库表名是否出现了小写字母或者关键字,如USER

  4. python logging 模块的应用

    对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不熟悉的应用为何不工作.有时候,你甚至不知道系统运行,是否跟原始设计一致. 在线运行的应用就是黑盒子,需要被跟踪监控.最简单也最重要的方式就是记录日志. ...

  5. 20165228 2017-2018-2 《Java程序设计》第7周学习总结

    20165228 2017-2018-2 <Java程序设计>第7周学习总结 教材学习内容总结 MySQL数据库管理系统安装和初始化 使用MySQL建立连接和数据库.表 使用JDBC:(1 ...

  6. TCP三次握手及释放连接详解(转)

    一.TCP头部简介 ACK :即确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符.表示发来的数据已确认接收无误.TCP报文格式中的控制位由6个标志比特构成,其中一个就是ACK,ACK为1表 ...

  7. Django之模板层-自定义过滤器以及标签

    自定义标签与过滤器 在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag. 在app中创建templatetags模块(模块名只能是t ...

  8. TFLearn 与 Tensorflow 一起使用

    好用的不是一点点..=-=.. import tensorflow as tf import tflearn import tflearn.datasets.mnist as mnist # Usin ...

  9. Unity调用Windows对话框保存时另存为弹框

    Unity开发VR之Vuforia 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- ...

  10. codefoce Cooking Time

    #include <bits/stdc++.h> using namespace std; struct T { // 贪心 优先弹出相邻靠后的材料 int id; int p; bool ...