CF207C
前言
学习 zzd 博客(
这题超级没有素质。
连个题解都搜不到。
好不容易搜到一个。
看了一下是 pascal。
不过还好我有办法。
树剖做 级祖先。
十万的俩老哥飘过。
三百毫秒优异成绩。
哇哈哈哈哈哈哈哈。
接下来该放代码了。
做法描述在代码后。
Code
这个能做到 的情况。
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; }
思路
考虑答案的两种描述:
- 的叶到根的路径,与 的祖先往叶的路径相等。
- 的根到叶的路径,与 的叶往上的路径相等。
第二种信息更好维护,考虑按第二种方式计算答案。
先假设 一开始给定,不会修改。
我们考虑加 中叶子的操作,如何快速查询答案变化量。
对 做树上后缀排序,查询操作可以描述为在后缀数组上查找一段合法的区间。
再考虑上加 叶子的操作,我们把操作离线。
然后我们把每次的加 叶子操作带来的贡献分为两类。
- 其加入时带来的贡献。
- 其对未来的贡献。
其加入时的贡献可以在后缀数组上二分找到合法区间,查询其中时间戳不大于插入时间的数的个数;同时给区间打上 “当区间内加入新节点时,对答案整体加 ” 的标记。
这个可以用 BIT 维护。
于是核心问题在于:如何二分?
注意到其二分的左右端点均不大于父亲,我们只用考虑新的一位带来的贡献。
这个东西就容易二分了;查找区间内向上为某位的方案数,可以用树上 级祖先的方法快速查询。
不过如果用树剖实现 级祖先,常数会很小,比 的长剖还要快些。
因此核心问题在于树上后缀排序,这个可能会恶心一点。
如果用后缀平衡树实现会很难写(要手写替罪羊树),而且一个不巧就爆 double
精度了。
直接写倍增法,即可在 时间内离线建出树上后缀数组。
总复杂度 或 ,瓶颈在二分。
空间复杂度为线性,且与字符集无关。
(听说有高明哈希做法与高明广义 SAM 做法)
本文来自博客园,作者:myee,转载请注明原文链接:https://www.cnblogs.com/myee/p/Luogu-solution-cf207c.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫