【CF1009F Dominant Indices】(长链剖分)
题意
给定一棵以 为根, 个节点的树。设 为 子树中到 距离为 的节点数。
对于每个点,求一个最小的 ,使得 最大。
。
思路
考虑朴素的 dp 转移,即:
时间复杂度为 ,利用线段树合并或树上启发式合并的方法可以优化到 ,这里主要介绍一种长链剖分优化的方式。
长链剖分,即设定 的重儿子 :满足子树 中存在深度最大的节点的儿子。那么对于每一个顶点节点,从它前往深度最深的节点的路径就是一条重链。
可以证明,这样划分满足任意一个节点到根节点的路径被不超过 条重链覆盖。
回到本题中,对于一个节点 ,注意到当它只有一个儿子 的时候,信息可以直接转移过来。考虑对状态定义进行优化,设 为,与 子树中最深的那个节点的深度差为 的节点个数。那么每次就可以直接继承重儿子的信息。
而对于其余的轻儿子,即其他重链的顶点节点,直接暴力枚举深度合并答案。由于每条重链最多只会被合并一次,所以时间复杂度为
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e6+10;
vector<int>f[N];
int n,dep[N],son[N],ans[N],h[N],idx;
struct edge{int v,nex;}e[N<<1];
void add(int u,int v){e[++idx]=edge{v,h[u]};h[u]=idx;}
void dfs(int u,int fa)
{
for(int i=h[u];i;i=e[i].nex)
{
int v=e[i].v;if(v==fa) continue;dfs(v,u);
if(!son[u]||dep[v]>dep[son[u]]) son[u]=v,dep[u]=dep[v]+1;
}
}
void solve(int u,int fa)
{
if(!son[u]) return f[u].push_back(1),ans[u]=0,void();solve(son[u],u);
swap(f[u],f[son[u]]),ans[u]=ans[son[u]];f[u].push_back(1);//vector swap为O(1)
if(f[u][ans[u]]==1) ans[u]=dep[u];//特判一条链的情况
for(int i=h[u];i;i=e[i].nex)
{
int v=e[i].v;if(v==fa||v==son[u]) continue;solve(v,u);
for(int j=dep[v];j>=0;j--)
{
int tmp=dep[u]-(dep[v]-j+1);f[u][tmp]+=f[v][j];
if(f[u][tmp]>f[u][ans[u]]||f[u][tmp]==f[u][ans[u]]&&tmp>ans[u]) ans[u]=tmp;
}
}
}
int main()
{
scanf("%d",&n);for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);dfs(1,0);solve(1,0);
for(int i=1;i<=n;i++) printf("%d\n",dep[i]-ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通