前言

学习 zzd 博客

这题超级没有素质。

连个题解都搜不到。

好不容易搜到一个。

看了一下是 pascal。

不过还好我有办法。

树剖做 \(k\) 级祖先。

十万的俩老哥飘过。

三百毫秒优异成绩。

哇哈哈哈哈哈哈哈。

接下来该放代码了。

做法描述在代码后。

Code

这个能做到 \(n_1,n_2\le300000\) 的情况。

uint FathA[300005],FathB[300005],SA[300005],Rank[300005],tp1,tp2;
chr C_A[300005],C_B[300005];
voi build(){
static uint Fath[300005],Fath2[300005];
uint n=tp2;for(uint i=0;i<n;i++)Fath[i]=FathB[i],SA[i]=i;
std::sort(SA,SA+n,[&](uint a,uint b){return C_B[a]<C_B[b];});
for(uint i=0;i<n;i++)Rank[SA[i]]=i&&C_B[SA[i]]==C_B[SA[i-1]]?Rank[SA[i-1]]:i;
for(uint len=1;len<n;len<<=1){
static std::pair<uint,uint>W[300005];
bol ok=true;
for(uint i=0;i<n;i++){
W[i]={Rank[i],~Fath[i]?Rank[Fath[i]]+1:0u};
if(~Fath[i])Fath2[i]=Fath[Fath[i]],ok=false;
else Fath2[i]=Fath[i];
}
for(uint i=0;i<n;i++)Fath[i]=Fath2[i];
if(ok)break;
static uint Cnt[300005];
for(uint i=0;i<=n;i++)Cnt[i]=0;
for(uint i=0;i<n;i++)Cnt[W[i].second]++;
for(uint i=1;i<=n;i++)Cnt[i]+=Cnt[i-1];
for(uint i=n-1;~i;i--)Rank[--Cnt[W[i].second]]=i;
for(uint i=0;i<n;i++)Cnt[i]=0;
for(uint i=0;i<n;i++)Cnt[W[i].first]++;
for(uint i=1;i<n;i++)Cnt[i]+=Cnt[i-1];
for(uint i=n-1;~i;i--)SA[--Cnt[W[Rank[i]].first]]=Rank[i];
for(uint i=0;i<n;i++)Rank[SA[i]]=i&&W[SA[i]]==W[SA[i-1]]?Rank[SA[i-1]]:i;
}
for(uint i=0;i<n;i++)SA[i]=i;
std::sort(SA,SA+n,[&](uint a,uint b){return Rank[a]<Rank[b];});
for(uint i=0;i<n;i++)Rank[SA[i]]=i;
}
uint Siz[300005],Heavy[300005],Dep[300005],Rot[300005],Dfn[300005],Back[300005];
voi build2(){
static std::vector<uint>Son[300005];
static uint Fath[300005],Cur[300005];
uint n=tp2;
for(uint i=0;i<n;i++)Fath[i]=FathB[i],Heavy[i]=Rot[i]=Cur[i]=-1,Siz[i]=1;
for(uint i=1;i<n;i++)Dep[i]=Dep[Fath[i]]+1,Son[Fath[i]].push_back(i);
for(uint i=n-1;i;i--){
if(!~Heavy[Fath[i]]||Siz[Heavy[Fath[i]]]<Siz[i])Heavy[Fath[i]]=i;
Siz[Fath[i]]+=Siz[i];
}
std::vector<uint>P{0};uint cnt=0;
while(P.size()){
uint p=P.back();
if(!~Rot[p])Rot[p]=p;
if(!~Cur[p]){
Cur[p]=0,Back[Dfn[p]=cnt++]=p;
if(~Heavy[p]){Rot[Heavy[p]]=Rot[p],P.push_back(Heavy[p]);continue;}
}
while(Cur[p]<Son[p].size()&&Son[p][Cur[p]]==Heavy[p])Cur[p]++;
if(Cur[p]==Son[p].size()){P.pop_back();continue;}
P.push_back(Son[p][Cur[p]++]);
}
}
chr kthFath(uint p,uint d){
if(d>=Dep[p])return'\0';
while(Dfn[p]-Dfn[Rot[p]]<d)d-=Dfn[p]-Dfn[Rot[p]]+1,p=FathB[Rot[p]];
return C_B[Back[Dfn[p]-d]];
}
uint L[300005],R[300005];
voi build3(){
static uint Dep[300005];
L[0]=0,R[0]=tp2,Dep[0]=-1;
for(uint p=1;p<tp1;p++){
Dep[p]=Dep[FathA[p]]+1;
uint l=L[FathA[p]],r=R[FathA[p]];
while(l<r){
uint mid=(l+r)>>1;
if(kthFath(SA[mid],Dep[p])>=C_A[p])r=mid;else l=mid+1;
}
L[p]=l;
r=R[FathA[p]];
while(l<r){
uint mid=(l+r)>>1;
if(kthFath(SA[mid],Dep[p])>C_A[p])r=mid;else l=mid+1;
}
R[p]=l;
}
}
namespace BIT1{
const uint Lim=400000;
ullt B[Lim+5];
voi add(uint p,ullt v){
p++;while(p<=Lim)B[p]+=v,p+=lowbit(p);
}
ullt find(uint l,uint r){
ullt ans=0;
while(r)ans+=B[r],r-=lowbit(r);
while(l)ans-=B[l],l-=lowbit(l);
return ans;
}
}
namespace BIT2{
const uint Lim=400000;
ullt B[Lim+5];
voi add(uint l,uint r,ullt v){
l++,r++;
while(l<=Lim)B[l]+=v,l+=lowbit(l);
while(r<=Lim)B[r]-=v,r+=lowbit(r);
}
ullt find(uint p){
p++;
ullt ans=0;
while(p)ans+=B[p],p-=lowbit(p);
return ans;
}
}
uint Op[600005];ullt Ans[600006];
int main()
{
uint q;scanf("%u",&q);Op[0]=1,Op[1]=2,tp1=tp2=1,q+=2,FathA[0]=FathB[0]=-1;
for(uint i=2;i<q;i++){
scanf("%u",Op+i);
if(Op[i]==1)scanf("%u%s",FathA+tp1,C_A+tp1),FathA[tp1++]--;
else scanf("%u%s",FathB+tp2,C_B+tp2),FathB[tp2++]--;
}
build();
build2();
build3();
for(uint i=0,p1=0,p2=0;i<q;i++){
if(Op[i]==1){
Ans[i]=BIT1::find(L[p1],R[p1]);
BIT2::add(L[p1],R[p1],1);
p1++;
}
else{
Ans[i]=BIT2::find(Rank[p2]);
BIT1::add(Rank[p2],1);
p2++;
}
}
for(uint i=1;i<q;i++)Ans[i]+=Ans[i-1];
for(uint i=2;i<q;i++)printf("%llu\n",Ans[i]);
return 0;
}

思路

考虑答案的两种描述:

  • \(t_1\) 的叶到根的路径,与 \(t_2\) 的祖先往叶的路径相等。
  • \(t_1\) 的根到叶的路径,与 \(t_2\) 的叶往上的路径相等。

第二种信息更好维护,考虑按第二种方式计算答案。

先假设 \(t_2\) 一开始给定,不会修改。

我们考虑加 \(t_1\) 中叶子的操作,如何快速查询答案变化量。

对 \(t_2\) 做树上后缀排序,查询操作可以描述为在后缀数组上查找一段合法的区间。

再考虑上加 \(t_2\) 叶子的操作,我们把操作离线。

然后我们把每次的加 \(t_1\) 叶子操作带来的贡献分为两类。

  1. 其加入时带来的贡献。
  2. 其对未来的贡献。

其加入时的贡献可以在后缀数组上二分找到合法区间,查询其中时间戳不大于插入时间的数的个数;同时给区间打上 “当区间内加入新节点时,对答案整体加 \(1\)” 的标记。

这个可以用 BIT 维护。

于是核心问题在于:如何二分?

注意到其二分的左右端点均不大于父亲,我们只用考虑新的一位带来的贡献。

这个东西就容易二分了;查找区间内向上为某位的方案数,可以用树上 \(k\) 级祖先的方法快速查询。

不过如果用树剖实现 \(k\) 级祖先,常数会很小,比 \(O(1)\) 的长剖还要快些。

因此核心问题在于树上后缀排序,这个可能会恶心一点。

如果用后缀平衡树实现会很难写(要手写替罪羊树),而且一个不巧就爆 double 精度了。

直接写倍增法,即可在 \(O(n\log n)\) 时间内离线建出树上后缀数组。

总复杂度 \(O(n\log n)\) 或 \(O(n\log^2n)\),瓶颈在二分。

空间复杂度为线性,且与字符集无关。

(听说有高明哈希做法与高明广义 SAM 做法)

CF207C的更多相关文章

随机推荐

  1. 启动springboot项目报错Unable to start embedded Tomcat

    1.问题描述 最近在学习springcloud的时候,在父工程下新建一个model后,引入dashboard相关依赖后启动报错 2.产生原因 产生原因有可能就是pom.xml中下载的jar包版本冲突 ...

  2. python之路30 网络编程之初识并发编程1

    并发编程理论 研究网络编程其实就是在研究计算机的底层原理及发展史 """ 计算机中真正干活的是CPU """ 操作系统发展史 1.穿孔卡片阶 ...

  3. MySQL join语句怎么优化?

    在MySQL的实现中,Nested-Loop Join有3种实现的算法: 1. Simple Nested-Loop Join:简单嵌套循环连接 2. Block Nested-Loop Join:缓 ...

  4. 【Redis实战专题】「性能监控系列」全方位探索Redis的性能监控以及优化指南

    Redis基本简介 Redis是一个开源(BSD 许可).内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理.它支持字符串.哈希表.列表.集合.有序集合等数据类型.内置复制.Lua 脚本. ...

  5. Wireshark嗅探软件

    # Wireshark的基本使用方法 1.1 打开wireshark 1.2 查看eth0 1.3 混杂模式 混杂模式 :任何经过这台主机的数据报都会被捕获. 停止捕获 -> 捕获 -> ...

  6. MAC实用操作记录---使用命令执行文件上传下载解压打包与解包

    1.使用命令执行文件上传下载 https://www.cnblogs.com/sugartang/p/12743470.html 2.提示:打不开xx软件,在 OS X 安装软件打不开提示" ...

  7. hashlib加密模块及subprocess远程命令模块

    hashlib加密模块及subprocess远程命令模块 一.hashlib加密模块 1.加密模块简介 1.加密模块简介 将明文数据进行加密处理,转变为密文数据再存储或者传输,这样的安全机制可以让用户 ...

  8. Dubbo 中 Zookeeper 注册中心原理分析

    vivo 互联网服务器团队- Li Wanghong 本文通过分析Dubbo中ZooKeeper注册中心的实现ZooKeeperResitry的继承体系结构,自顶向下分析了AbstractRegist ...

  9. 在腾讯云上创建一个玩具docker-mysql数据服务

    有时候开发需求会自己做一下测试数据,在自己电脑本地安装的服务多了电脑环境会搞的很乱,这时使用云服务器安装个docker服务是一个不错的寻找. 下面步骤是在腾讯云上安装docker-mysql镜像,并导 ...

  10. C#代码整洁之道读后总结与感想

    1. 基本信息 C#代码整洁之道:代码重构与性能提升 ,英文名为Clean Code in C#. 作者:[英] 詹森·奥尔斯(Jason Alls) 著,刘夏 译 机械工业出版社,2022年4月出版 ...