长链剖分
能 时空解决部分 dsu on tree / 线段树合并 / 倍增 时空的问题。
长链剖分的机制是选子树内深度最大的儿子作为长儿子(类比重链剖分的重儿子)。
然后有几个性质:
-
一条链向上跳一条链,则链上最大深度不减。
-
由 1 可知任何一个点向上跳链次数不会超过 次。(但是这个性质不是很有用)。
-
任意一个点 的 次祖先 所在的长链的长度大于等于 ,因为 长度为 ,长儿子必然更长。
P5903 【模板】树上 k 级祖先
对树进行长链剖分,记录每个点所在链的顶点和深度。
树上倍增求出每个点的 级祖先。
对于每条链,如果其长度为 ,那么在顶点处记录顶点向上的 个祖先和向下的 个链上的儿子。
预处理出每个数的二进制最高位。
对于每次询问 的 级祖先:
利用倍增数组先将 跳到最大的 级祖先。
设剩下还有 。
由刚刚的性质,此时 所在长链长度 。
因此可以先将 跳到 所在链的顶点,若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。
复杂度为 。
点击查看代码
/* * Author: ShaoJia * Create Time: 2022-09-01 21:12:55 * Last Modified time: 2022-09-01 21:53:13 * Motto: We'll be counting stars. */ //#pragma GCC optimize("Ofast") #include<bits/stdc++.h> using namespace std; #define fir first #define sec second #define mkp make_pair #define pb emplace_back #define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++) #define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--) #define ckmx(a,b) a=max(a,b) #define ckmn(a,b) a=min(a,b) #define debug(...) cerr<<"#"<<__LINE__<<": "<<__VA_ARGS__<<endl #define ui unsigned int ui S; inline ui get(ui x) { x ^= x << 13; x ^= x >> 17; x ^= x << 5; return S = x; } //------------------------- #define C 18 #define N 500010 int n,q,root,dep[N],md[N],wson[N],top[N],f[N][C+1],lg[N]; vector<int> e[N],up[N],dn[N]; void dfs(int rt,int fa){ f[rt][0]=fa; For(i,1,C) f[rt][i]=f[f[rt][i-1]][i-1]; md[rt]=dep[rt]=dep[fa]+1; for(int i:e[rt]){ dfs(i,rt); ckmx(md[rt],md[i]); if(md[wson[rt]]<md[i]) wson[rt]=i; } } void dfs2(int rt,int tp){ top[rt]=tp; if(rt==tp){ int x=rt; For(i,0,md[rt]-dep[rt]){ up[rt].pb(x); x=f[x][0]; } x=rt; For(i,0,md[rt]-dep[rt]){ dn[rt].pb(x); x=wson[x]; } } if(wson[rt]) dfs2(wson[rt],tp); for(int i:e[rt]) if(i!=wson[rt]) dfs2(i,i); } int que(int x,int y){ if(!y) return x;//0 dont have lg x=f[x][lg[y]]; y-=1<<lg[y]; y-=dep[x]-dep[top[x]]; x=top[x]; return y>0?up[x][y]:dn[x][-y]; } signed main(){ios::sync_with_stdio(false),cin.tie(nullptr); cin>>n>>q>>S; For(i,2,n) lg[i]=lg[i>>1]+1; int x,k,ans=0; long long out=0; For(i,1,n){ cin>>x; if(x) e[x].pb(i); else root=i; } dfs(root,0); dfs2(root,root); For(i,1,q){ x=(get(S)^ans)%n+1; k=(get(S)^ans)%dep[x]; out^=(long long)i*(ans=que(x,k)); } cout<<out<<endl; return 0;}
P3899 [湖南集训]更为厉害
长链剖分优化 DP。
时空 。
相当于按 启发式合并(?)
点击查看代码
/* * Author: ShaoJia * Last Modified time: 2022-09-02 10:54:28 * Motto: We'll be counting stars. */ #include<bits/stdc++.h> using namespace std; #define pb emplace_back #define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++) #define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--) #define ll long long #define pi pair<int,int> const int N=300010; int n,q,dep[N],wson[N],md[N],sz[N]; ll ans[N],*f[N];//f: dp's sufsum vector<int> e[N]; vector<pi> g[N]; void dfs(int rt,int fa){ dep[rt]=dep[fa]+1; sz[rt]=1; for(int i:e[rt]) if(i!=fa){ dfs(i,rt); sz[rt]+=sz[i]; if(md[i]>md[wson[rt]]) wson[rt]=i; } if(wson[rt]) md[rt]=md[wson[rt]]; else md[rt]=dep[rt]; } void work(int rt,int fa,int tp){ if(rt==tp) f[rt]=new ll[md[rt]-dep[rt]+1];//top f[rt][0]=sz[rt]-1; if(wson[rt]){ f[wson[rt]]=f[rt]+1; work(wson[rt],rt,tp); f[rt][0]+=f[wson[rt]][0]; } for(int i:e[rt]) if(i!=fa && i!=wson[rt]){ work(i,rt,i); For(j,0,md[i]-dep[i]) f[rt][j+1]+=f[i][j]; f[rt][0]+=f[i][0]; } for(auto [k,x]:g[rt]){ ans[x]=(ll)(sz[rt]-1)*min(dep[rt]-1,k);//b is anc of a ans[x]+=f[rt][0]-(sz[rt]-1); if(k<md[rt]-dep[rt]) ans[x]-=f[rt][k+1];//sufsum get range sum } } signed main(){ios::sync_with_stdio(false),cin.tie(nullptr); cin>>n>>q; int x,y; For(i,1,n-1){ cin>>x>>y; e[x].pb(y); e[y].pb(x); } dfs(1,0); For(i,1,q){ cin>>x>>y; g[x].pb(y,i); } work(1,0,1); For(i,1,q) cout<<ans[i]<<endl; return 0;}
CF570D Tree Requests
跳坑:传 wson 时忘记 f[wson[rt]]=f[rt]+1;
,求答案时忘记判 k>md[rt]-dep[rt]
。
CF526G Spiders Evil Plan
复合题。
题意简述
给定一棵 个节点的无根树,每条边有边权。
有 次询问,每次询问给出 ,你需要选择 条树上的路径,使这些路径形成一个包含 的连通块,且连通块中包含的边 权和最大。
强制在线。
- 。
题解
可以证明:使用 条路径就可以覆盖一棵有 的叶子的树。
先以任意方式匹配叶子。如果有两条路径不相交,可以调整成相交的情况。
不断调整就可以让任意两条路径都相交,于是显然覆盖了整棵树。
(证明不严谨,因为没有说明调整能在有限步内结束,不过这不重要)
所以当询问 的时候,就是要在原树中选取不超过 个叶子,让这些叶子组成的极小连通块的边权和尽量大。
再考虑:每次询问中,一定存在一种方案使得直径的两端中至少有一端被选取。
那么我们以两个直径端点为根,每次询问在两棵树中分别查询即可。
那么,现在根是一个叶子(直径端点必然是叶子),且根必选。
也就是说,需要选其它至多 个叶子,打通他们到根的链,并且最大化边权和。
考虑带边权的长链剖分,发现这和选取过程是等价的,也就是贪心地选取前 个最长链即可。
但是选完之后不一定经过 ,所以需要做一下调整。
首先打通 子树中最深的点到根的路径,然后需要去掉另一个叶子,使得减小量尽量小。
可以发现,要不然是删掉第 个最长链,也就是仅选取前 个最长链,然后把 接上。
要不然就是在选取前 个最长链的基础上,先把 接上,然后删去第一个碰到的点的其它子树。
最优解,一定符合这两种情况之一,且不会统计到错误的情况(叶子数都不超过 ),所以是正确的。
向上跳的过程可以倍增,但是我喜欢省空间,写了一个重剖。
时间复杂度为 ,空间倍增 ,树剖 。
CF1009F Dominant Indices
本文来自博客园,作者:ShaoJia,版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义