把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

题解 AT_ddcc2017_final_e 足のばし

洛谷

题意

有一棵 N 个结点的树,初始每一条边的长度都为 1。有一种操作,会使某一条边的长度增加 1。给定 Q 次询问,每次给出询问 Ki,问正好操作 Ki 次后,最短树直径。(询问之间独立)。

对于 45% 的数据,N3000,Q3000,Ki3×106

3N2×1051ai,biN1Q2×1050Ki1018

分析

为了行文方便,下文中原直径长度为 dis

首先可以想到的是先将整棵树的直径处理出来,然后对于直径上的节点可以先进行在不影响直径长度下的最长补充,这是我们在这个直径下的最多的 K

接下来继续分析,我们会发现,显然,再操作一次后,直径必然增加 1,那么我们应该可以想到,我们要使直径在 dis+1 的操作数尽量大,贪心上的,我们可以往价值最大的方向移动。

这是我们的整体的贪心思路,接下来是实现。

首先是我们发现,在直径上,有个特殊的节点,那就是我们直径的中点,中点的链接的各个节点在补全后应当最大长度是一个相同的值,但是这个中点却有可能是在一条边上,因此,我们引进一个中间节点,将一条边 (u,v),分成 (u,k),(k,v),于是我们的中间点就变成了一个真正的节点。这就是第一部分,建图,拆边。此时,直径就是以中心结点为根的最长链。

此时,问题是我们的补全,好发现的,我们最优条件下就是将所有操作加在叶子结点链接的那一条边上,因此我们可以操作的最大数就是(叶子结点的数量 × 最长的链长 所有叶子结点的深度和)/2(因为拆边),第二部分,补充。

接下来就是扩张直径。

从上边可以看出,我们对某一条操作后(令其链接的叶子结点方向的与中心结点相连的结点为 v,原中心为 u),我们的中心点必然会往 v 方向移动,也就是根结点会切换成 v,对于 u 折下来的这一边的子树,我们无法进行补充,因此,往 v 方向,我们一共可以扩展 lfv 次操作(lfv 为以 v 为根的子树的叶子结点个数,在换根的同时维护)。第三部分,换根。

我们就可以维护出每一次增长直径是的最大操作数,可以拿下 Ki 比较小的情况。

那么 Ki 比较大的时候又如何解决呢?我们观察换根部分。

可以发现,假如我们当前结点是 u,此时将转移向 v,那么无论任何时候,我们都应当转移到 v,因为我们的 lf 数组必然在相同根时相等。于是,我们的这个换根最后必然会陷入一个两个结点形成的循环,而这两个结点的操作数我们也是知道的,所以在 lf 较大时,我们可以用循环节解决。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5+5;
const int M = 2e6+5;
inline int read() {
int x;
scanf("%lld",&x);
return x;
}
int n, m,cnt,dep[N],lf[N],fa[N],tot,f[M];
vector<int > lj[N];
inline void add(int u,int v) {
lj[u].push_back(v),lj[v].push_back(u);
}
inline void dfs(int now,int fath) {
dep[now]=dep[fa[now]=fath]+1,lf[now]=0;
if(lj[now].size()==1&&fath) lf[now]=1,tot+=dep[now];
for(auto to:lj[now]) if(to!=fath) dfs(to,now),lf[now]+=lf[to];
}
int st[M],top;
inline void redfs(int now,int fath) {
int mxto=0,mx=0;
for(auto to:lj[now]) if(mx<lf[to]) mx=lf[to],mxto=to;
st[++top]+=mx,tot+=mx;
if(mxto==fath) return ;
lf[now]-=lf[mxto],lf[mxto]+=lf[now];
redfs(mxto,now);
}
signed main() {
freopen("V.in","r",stdin);
freopen("V.out","w",stdout);
n=read(),m=n*2-1;
for(int i=1; i<n; ++i) {
int u=read(),v=read();
add(u,n+i),add(v,n+i);
}
dep[0]=-1,tot=0;
dfs(1,0);
int A=1,B;
for(int i=1; i<=m; ++i) if(dep[A]<dep[i]) A=i;
dfs(B=A,0);
for(int i=1; i<=m; ++i) if(dep[A]<dep[i]) A=i;
int md=tot=0,dis=dep[A]/2;
for(int now=A; now; now=fa[now]) if(dep[A]/2==dep[now]) md=now;
dfs(md,0);
tot=f[0]=(lf[md]*dis-tot)/2;
redfs(md,0);
for(int i=1; i<=top; ++i) f[i]=f[i-1]+st[i];
int Q=read();
while(Q--) {
int x=read();
if(x<=f[top]) {
int res=dis+lower_bound(f,f+top+1,x)-f;
cout<<res<<"\n";
} else {
int res=dis+top;
x-=f[top];
res+=2*(x/(st[top]+st[top-1]));
x%=(st[top]+st[top-1]);
if(x) ++res;
if(x>st[top-1]) ++res;
cout<<res<<"\n";
}
}
return 0;
}
posted @   djh0314  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示