1 条题解
-
0
自动搬运
来自洛谷,原作者为

光明正大
AFO搬运于
2025-08-24 22:16:58,当前版本为作者最后更新于2020-02-11 15:24:06,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
二分答案!!!
比赛时看到此题仔细琢磨,感觉和NOIP 2018赛道修建有些类似
都是在分一定段的前提下使某个值尽可能地小(大)。
二分答案:显然是对需要时间最长的人所需要的时间,即要求的答案 进行二分。
如果 越大,那么每个人可以访问到的节点就一定不会更少,需要的人力就越少,答案具有可二分性。
所以对于每个,我们求出所需要的的人数,
如果 <= ,说明在规定时间内完成任务,则接着在左半边找;
如果 > ,说明在规定时间完不成任务,则接着在右半边找。
问:在每次时,我们想让一个人到达尽可能多的叶子节点,如何快速的求出从叶子s能到达的距s最远的节点呢?
答:二分答案!
这一点与赛道修建比较相似,在总的二分答案的check中再次二分。
设 走到 花费时间 ,
若 <= 则说明时间没用完,在 到 之间找;
若 > 则说明时间不够用,在 到 之间找。
显然在寻找最远能走到的叶子的过程中,也具有可二分性。
这里其实还有一个小问题:
如何快速地()求出两个叶子之间的距离呢?
设 表示 到 的距离,
对于树上任意两点 , ,
它们之间的距离为 :
+ - 2*
数组的求解可在LCA的预处理中进行。
设 表示从第一个叶子节点到第 个节点间的距离,
若一个人把 到 之间的点都访问了一遍,那么他所需的时间为:(自己画个图就能明白了
其实是我懒得画图了)数组也可以快速提前处理。
也许有人还有问题,如何保证叶子结点的顺序呢?
题目中有一句话:“本质上,题目给出的树就是住址构成的 字典树/Trie树 ”。
这就保证了按照读入的顺序跑一遍 依次访问到的是编号从大到小的叶子结点(因为链式前向星是倒序遍历的,不过正倒并不影响)。
我在程序另开一个数组 其中 表示从大到小第i个叶子结点的编号:
设叶子结点为 (实际上最多为 ),值域为 ,
总复杂度为
下面是我的代码:仅供参考,略有压行,大佬不喜勿喷
#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=2e5+10; struct EDGE{int to,next;ll w;} e[maxn<<1]; bool nl[maxn];//not leaf int n,m,k,root,cnt,dep[maxn],fa[maxn][22],Log2[maxn],head[maxn]; ll dis[maxn],s[maxn]; void add(int u,int v,ll w) {e[++cnt].to=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;} inline void dfs(int u,int f) { dep[u]=dep[f]+1;fa[u][0]=f; for(int i=1;(1<<i)<=dep[u];i++) fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head[u];i;i=e[i].next) { if(e[i].to!=f) nl[u]=1, dis[e[i].to]=dis[u]+e[i].w,dfs(e[i].to,u); } } inline int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); while(dep[x]>dep[y]) x=fa[x][Log2[dep[x]-dep[y]]-1]; if(x==y) return x; for(int k=Log2[dep[x]]-1;k>=0;k--) if(fa[x][k]!=fa[y][k]) x=fa[x][k],y=fa[y][k]; return fa[x][0]; } int st[maxn],tot;//st[]:存叶子几点的编号 inline void DFS(int u)//找到所有叶子结点 { for(int i=head[u];i;i=e[i].next) if(e[i].to!=fa[u][0]) DFS(e[i].to); if(!nl[u]) st[++tot]=u; } inline ll calc(int u,int v) {return s[v]+dis[st[v]]-s[u]+dis[st[u]];} inline int erfen(int s,ll x)//对能走到的位置二分 { int L=s,R=tot,res=s-1; while(L<=R) { int mid=(L+R)>>1; if(calc(s,mid)<=x) res=mid,L=mid+1; else R=mid-1; } return res; } inline bool check(ll x) { int last=0,temp=0; for(int i=1;i<=k;i++) { temp=erfen(last+1,x); if(temp==last) return 0; if(temp>=tot) return 1; last=temp; } return temp==tot; } int main() { scanf("%d%d",&n,&k); for(int i=1,x,y,w;i<n;i++) { scanf("%d%d%d",&x,&y,&w); add(x,y,w);add(y,x,w); } dfs(1,0);DFS(1); for(int i=1;i<=n;i++) Log2[i]=Log2[i-1]+(1<<Log2[i-1]==i); for(int i=2;i<=tot;i++) //求解s数组 s[i]=s[i-1]+dis[st[i]]+dis[st[i-1]]-dis[LCA(st[i-1],st[i])]*2LL; ll l=1,r=1LL<<60,ans=1LL<<60; while(l<=r) {//对答案二分 ll mid=(l+r)>>1; if(check(mid)) ans=mid,r=mid-1; else l=mid+1; } printf("%lld",ans); return 0; }代码中有些小细节还需特别注意, 总的来说是一道很不错的二分答案的题目
- 1
信息
- ID
- 5086
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 4
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者