长链剖分学习笔记
什么事长链剖分:
- 对于每个节点令其子树高度最大的儿子的边为实边,其余边为虚边。
- 于是树可以剖成由实边组成的若干长链。
- 同重剖类似,有一个结论:对于任意节点 ,其到根的路径的上的虚边数量(长链数量)是 级别。
CF1009F Dominant Indices
对于每个节点 ,求出一个最小的 ,使得 子树中到 距离为 的点的数量最大。
对于每个点 表示 子树内到 的距离为 的节点个数。如果直接存储会炸裂。我们发现其实求 的信息完全可以直接利用一些在子节点求得的信息然后再往上增量。这点信息,不需要 copy,直接用指针即可。
我们考虑对于每一个长链申请一个空间。从指针理解, 表示 数组的头指针。对于节点 ,设它的实边儿子为 ,则让 ,即长成这样:
然后对于每个节点,我们不仅从 上直接得到信息,还需要合并所有虚边连向的长链。这个普通转移即可。
对于复杂度,每条长链最多被合并一次,而合并一次的复杂度为其链长,所以总复杂度为 。而空间复杂度也一样,每条链所需空间为链长,同样得到 。
对于分配内存,维护一个指针,指向一个数组的位置,然后将长 的一段分配给它。
内存分配还是开大一点好。
https://codeforces.com/contest/1009/submission/133302570
const int N=1e6+9;
int n,h[N],son[N],g[N<<1],*f[N],ans[N],*cur=g;
vi e[N];
void dfs1(int u,int fa) {
for(auto v:e[u]) if(v!=fa) {
dfs1(v,u);
if(h[v]>=h[son[u]]) son[u]=v, h[u]=h[v]+1;
}
}
void dfs2(int u,int fa) {
if(son[u]) {
f[son[u]]=f[u]+1, dfs2(son[u],u), ans[u]=ans[son[u]]+1;
}
int mh=0;
for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
f[v]=cur, cur+=h[v]+1;
dfs2(v,u);
rep(i,0,h[v]) f[u][i+1]+=f[v][i];
mh=max(mh,h[v]+1);
}
f[u][0]=1;
rep(i,0,mh)
if(f[u][i]>f[u][ans[u]]||f[u][i]==f[u][ans[u]]&&i<ans[u]) ans[u]=i;
}
int main() {
n=read();
rep(i,2,n) {
int u=read(), v=read();
e[u].emplace_back(v), e[v].emplace_back(u);
}
dfs1(1,0);
f[1]=cur, cur+=h[1]+1; dfs2(1,0);
rep(i,1,n) printf("%d\n",ans[i]);
return 0;
}
经典题
求树上长度(路径点数)恰好为 的路径数量。
考虑在合并链的时候统计一下跨过 的链的数量即可。
const int N=3e5+9;
int n,k,ans,son[N],h[N],g[N<<1],*f[N],*cur=g;
vi e[N];
void dfs1(int u,int fa) {
for(auto v:e[u]) if(v!=fa) {
dfs1(v,u);
if(h[v]>=h[son[u]]) son[u]=v;
} h[u]=h[son[u]]+1;
}
void dfs2(int u,int fa) {
if(son[u]) {
f[son[u]]=f[u]+1, dfs2(son[u],u); if(h[u]>=k) ans+=f[u][k];
} f[u][0]++;
for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
f[v]=cur, cur+=h[v]+1;
dfs2(v,u);
rep(i,0,min(k,h[v])) if(k-i-1<=h[u]) ans+=f[u][k-i-1]*f[v][i];
rep(i,0,h[v]) f[u][i+1]+=f[v][i];
}
}
z
signed main() {
n=read(), k=read();
rep(i,2,n) {
int u=read(), v=read();
e[u].emplace_back(v), e[v].emplace_back(u);
}
dfs1(1,0), f[1]=cur, cur+=h[1]+1, dfs2(1,0);
printf("%lld\n",ans);
return 0;
}
POI2014 Hotels
求树上选三点两两距离相等的方案数。
设 表示子树中距离 为 的节点个数。然后考虑一种特殊情况,即三个点 (相互距离为 且总体 )满足 到 的距离加上 到 的距离等于 (即 到 的链跨过 )。针对这种情况,我们设计状态 表示存在多少这样的 满足到 距离相等(设之为 ),且 到 的距离为 。
所以答案为(若 同时出现,此时意味着 只是一个前缀,在和 合并时统计答案)
长剖增量 DP 即可。
const int N=4e5+9,mod=1e9+7;
int n,*f[N],*g[N],h[N],p[N],*cur=p,son[N],ans;
vi e[N];
void dfs1(int u,int fa) {
for(auto v:e[u]) if(v!=fa) {
dfs1(v,u);
if(h[v]>=h[son[u]]) son[u]=v;
}
h[u]=h[son[u]]+1;
}
void dfs2(int u,int fa) {
if(son[u]) {
f[son[u]]=f[u]+1, g[son[u]]=g[u]-1;
dfs2(son[u],u);
} f[u][0]=1; ans+=g[u][0];
for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
f[v]=cur, cur+=h[v]*2, g[v]=cur, cur+=h[v]*2;
dfs2(v,u);
rep(i,0,h[v]-1) ans+=g[u][i+1]*f[v][i], ans%=mod;
rep(i,1,h[v]-1) ans+=f[u][i-1]*g[v][i], ans%=mod;
rep(i,0,h[v]-1) g[u][i+1]+=f[u][i+1]*f[v][i];
rep(i,1,h[v]-1) g[u][i-1]+=g[v][i];
rep(i,0,h[v]-1) f[u][i+1]+=f[v][i];
}
}
signed main() {
n=read();
rep(i,2,n) {
int u=read(), v=read();
e[u].emplace_back(v), e[v].emplace_back(u);
}
dfs1(1,0), f[1]=cur, cur+=h[1]*2, g[1]=cur, cur+=h[1]*2, dfs2(1,0);
printf("%lld\n",ans);
return 0;
}
本文作者:LarsWerner
本文链接:https://www.cnblogs.com/TetrisCandy/p/15476318.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2020-10-28 To-Do List 2