CF1790F Timofey and Black-White Tree 题解

· · 题解

顶级诈骗题。

先放一个重要的结论:在将 x 个点染黑后,答案(即两两黑色点对的最小距离)为 \dfrac{n}{x} 级别(实际上界约为 \dfrac{2n}{x})。

考虑一个很显然的暴力乱搞,令一个 dis 数组 dis_i 表示离节点 i最近的一个黑色节点。每把一个点染黑,便从这个点开始做一遍搜索更新 dis 数组。若当前节点有更近的黑色节点,或当前节点离新加入的黑色节点的距离大于当前的答案,那么此时继续搜索没有意义,退出。

上述做法正确性显然,同时,这个看上去很假的东西复杂度是对的,为 \mathcal{O}(n\sqrt{n}),同时也是官方题解和 jiangly 的做法。

我们来分析一下复杂度:

首先在经过 \sqrt{n} 次操作后,答案不会超过 \dfrac{n}{\sqrt{n}}=\sqrt{n},因此所有有意义dis_i 的值都不会超过 \sqrt{n},在我们上述的操作过程中,dis_i 显然是单调不增的,因此在这之后,每个 dis_i 显然也只会被更新 \sqrt{n} 次,因此后面的操作总复杂度是 \mathcal{O}(n\sqrt{n}) 的。而前 \sqrt{n} 操作中,即使每次操作都满打满算单次复杂度 \mathcal{O}(n),那么 \sqrt{n} 次操作总复杂度也不过是 \mathcal{O}(n\sqrt{n}) 的。

因此该算法的复杂度是 \mathcal{O}(n\sqrt{n}) 的,可以通过本题,如果你超时了请尝试将搜索边界写的紧一点。

以上算法还能继续优化。

每次操作我们都进行一遍搜索的开销太大,我们考虑只进行向上更新,即对于每一个节点,我们一步步跳父亲并更新答案。

这个的正确性看上去不太显然,因为在加入节点时,其子树内的节点是否会被计算到?

答案是会,因为当其子树内部的点存在黑点时,在其向上更新的过程中,也会将这个点计算一遍答案,因此不会出现正确性上的问题。

至于复杂度,由于我们向上跳节点的次数不会超过当前答案(超过了一定不优),因此总复杂度为 \mathcal{O}(\sum_{i=1}^{n}ans_i),其中 ans_i 为加入第 i 个节点时的答案,初始黑点视为第一个节点。而我们知道 ans_i\leq \dfrac{n}{i},因此总复杂度为 \mathcal{O}(\sum_{i=1}^{n}\dfrac{n}{i}),这是一个经典的调和级数形式,因此复杂度为 \mathcal{O}(n\log n),本做法也是 tourist 的做法。

还有很多做法,比如大冤种点分树做法,与 CF342E 类似,不多展开,也是我赛时被诈骗写的做法

不过个人来看,本题不能完全算与上题重题,因为巧妙利用答案的上界解题思路是这题有而上题没有的。

```cpp #include<bits/stdc++.h> using namespace std; int t,n,s,ans,nxt[400005],head[200005],go[400005],k,dis[200005],a[200005]; void add(int u,int v) { nxt[++k]=head[u]; head[u]=k; go[k]=v; } void bfs(int s) { dis[s]=0; queue<int> q; q.emplace(s); while(!q.empty()) { int x=q.front(); q.pop(); if(dis[x]>=ans) break ; for(int i=head[x];i;i=nxt[i]) { int g=go[i]; if(dis[g]<=dis[x]+1) continue ; dis[g]=dis[x]+1; q.emplace(g); } } } int main() { cin>>t; while(t--) { cin>>n>>s; fill(dis+1,dis+1+n,n+1); fill(head+1,head+1+n,0); k=0; ans=n+1; for(int i=1;i<n;i++) { cin>>a[i]; } for(int i=1;i<n;i++) { int u,v; cin>>u>>v; add(u,v); add(v,u); } bfs(s); for(int i=1;i<n;i++) { ans=min(ans,dis[a[i]]); bfs(a[i]); cout<<ans<<" "; } cout<<"\n"; } } ``` $\mathcal{O}(n\log n)$ 实现: ```cpp #include<bits/stdc++.h> using namespace std; int t,n,s,ans,nxt[400005],head[200005],go[400005],k,dis[200005],a[200005],f[200005]; void add(int u,int v) { nxt[++k]=head[u]; head[u]=k; go[k]=v; } void add(int x) { int d=0,p=x; while(p&&d<ans) { ans=min(ans,dis[p]+d); dis[p]=min(dis[p],d); p=f[p]; d++; } } void dfs(int x,int fa) { f[x]=fa; for(int i=head[x];i;i=nxt[i]) { int g=go[i]; if(g!=fa) dfs(g,x); } } int main() { cin>>t; while(t--) { cin>>n>>s; fill(dis+1,dis+1+n,n+1); fill(head+1,head+1+n,0); k=0; ans=n+1; for(int i=1;i<n;i++) { cin>>a[i]; } for(int i=1;i<n;i++) { int u,v; cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); add(s); for(int i=1;i<n;i++) { ans=min(ans,dis[a[i]]); add(a[i]); cout<<ans<<" "; } cout<<"\n"; } } ```