【PER #1】捉迷藏 / Ptz2022 Day1.Kyoto U L 题解
今天心血来潮想改一改 pj 的题,发现了这场 easy round 的 A 还没改……
跟自己和解了,想了两天没想明白,说说大致思路。
只考虑一组询问怎么做,先把 \(v\) 当作根,称那些始终满足距离条件的点就做“备选点”。
首先第一步 \(B\) 肯定要选定一个儿子走下去,然后根据获得的距离信息再做抉择。假设现在走到了 \(u\),如果“备选点”还在 \(u\) 的子树内,那么跟第一步一样,还是需要选定一个儿子走下去。
所以最坏情况下,\(B\) 会往下走 \(h=\lfloor \frac{d-1}{2} \rfloor\) 步,此时再往下走就寄了。
同时,\(B\) 在“备选点”还在子树内的时候,不会在中途往回走。因为最终时间取决于 \(A\) 追上来的时间,\(B\) 只要确定终点,无论中途怎么走,\(A\) 都会朝 \(B\) 即终点方向走,除非 \(B\) 玩脱了让 \(A\) 堵住了通往终点的路。
所以现在相当于要确定一个从 \(v\) 往下长度为 \(h\) 的路径,当“备选点”还在 \(u\) 子树内时让 \(u\) 沿这条路径走。
当然,除了上面的情况,还有可能 \(B\) 走着走着发现“备选点”不在下面了,此时 \(B\) 有两种抉择:
-
铁了心往下面走
-
赶紧回头碰碰运气,看能不能在 \(A\) 追上来前走一条更长的路。
实际上第二种走法非常劣,如果选择了回头走更长的路,那还不如一开始就走这条更长的路,这样更不容易被 \(A\) 抓到,反正用时也一样(与前面分析不会往回走一样)。所以一旦 \(B\) 发现“备选点”不在下面了,就会往子树内最深的叶子走。
所以如上分析,\(B\) 一开始就会朝着最远点走,且一定会走 \(h\) 步。
假设 \(B\) 走 \(h\) 步后停在了 \(x\),现在,我们只需要分析最劣的那种情况————“备选点”在 \(x\) 子树内,然后算出答案。
(为什么是最劣的?因为上面说了“备选点”不在 \(x\) 子树内的话,\(B\) 就会往最远点走,而 \(A\) 当然不想便宜 \(B\),所以肯定会堵住这条路,即在 \(x\) 子树内)
这时 \(B\) 要么朝着没有“备选点”的儿子走,要么往上走。考虑怎么快速计算。
首先 \(v\) 的最远点一定是树的直径端点之一,可以快速计算。将 \(v\) 的最远点当作根,可以用倍增快速将 \(v\) 跳到 \(x\)。接着 \(x\) 的父亲肯定不能走(因为是最远点,上面一定有“备选点”),只需要考虑 \(x\) 的子树就行了。
设 \(x\) 是从 \(y\) 跳上来的,那么第二种情况相当于求 \(y\) 子树内的最深叶子,可以快速维护。
至于第一种情况,没有“备选点”的儿子相当于其最深叶子深度 \(< d-h-1\),可以用 vector 或 set 记录 \(x\) 每个儿子子树的最深叶子深度,排序后二分即可。
时间复杂度 \(\mathcal O(n\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using namespace my_std;
ll n,q,head[100010],cnt=0,dep[100010],rt;
struct node{
ll nxt,to;
}e[200020];
void add(ll u,ll v){
e[++cnt].nxt=head[u];
e[cnt].to=v;
head[u]=cnt;
}
struct tree{
vector<ll> vec[100010];
ll rt,dep[100010],maxdep[100010],f[100010][22];
void dfs(ll fa,ll u){
dep[u]=dep[fa]+1;
f[u][0]=fa;
fr(i,1,18) f[u][i]=f[f[u][i-1]][i-1];
go(u){
ll v=e[i].to;
if(v==fa) continue;
dfs(u,v);
maxdep[u]=max(maxdep[u],maxdep[v]+1);
vec[u].push_back(maxdep[v]+1);
}
sort(vec[u].begin(),vec[u].end());
}
void init(ll RT){
rt=RT;
dfs(0,rt);
}
ll jump(ll x,ll t){
pfr(i,18,0) if(t&(1ll<<i)) x=f[x][i];
return x;
}
ll query(ll x,ll d){
ll h=(d-1)/2,ans=0;
if(h){
x=jump(x,h-1);
ans=max(ans,d-h+maxdep[x]+1);
x=f[x][0];
}
ll tmp=lower_bound(vec[x].begin(),vec[x].end(),d-h)-vec[x].begin()-1;
if(tmp>=0) ans=max(ans,d-h+vec[x][tmp]);
else ans=max(ans,d-h);
return ans;
}
}t1,t2;
void dfs(ll fa,ll u){
dep[u]=dep[fa]+1;
if(dep[u]>dep[rt]) rt=u;
go(u){
ll v=e[i].to;
if(v==fa) continue;
dfs(u,v);
}
}
int main(){
n=read();
q=read();
fr(i,2,n){
ll u=read(),v=read();
add(u,v);
add(v,u);
}
dfs(0,1);
ll nrt=rt;
rt=0;
t1.init(nrt);
dfs(0,nrt);
t2.init(rt);
while(q--){
ll v=read(),d=read();
if(t1.dep[v]>=t2.dep[v]) writeln(t1.query(v,d));
else writeln(t2.query(v,d));
}
}
实际上本文有些地方不严谨,偏向感性(
主要是想了几天还是不会严谨证明,就摆烂了。有严谨证明的可以在下面回复,十分感谢!