[CF1695D]Tree Queries 题解
Preface
非常有意思的一道题,也是第一道让我破防的题。
代码无难度,想出来不算难,但搞清楚就很麻烦了。
Analysis
这道题乍一看没有任何思路,这时候就可以考虑从性质上入手。
我们观察到,如果我们指定了根节点,将这棵树转换为有根树,那么对于每个节点 \(u\),它的子树中至多有一个没有一个查询点。
不难理解,如果一棵以上的子树中无查询点,就算把其他点全部设为查询点也无法确定这几棵子树中的点。
根据这个条件设计算法。
枚举每个点作为根节点,进行树形 DP。
设 \(dp(u)\) 为 \(u\) 子树中最少的查询点个数,则有 \(dp(u) = \sum\limits_{v\in son_u}dp(v) + \max(x-1,0)\)
其中 \(x\) 为 \(dp(v) = 0\) 的 \(v\) 的数量。
说是 DP,其实也有贪心的思想,毕竟这是最理想的情况,但为何可行?
(注:下面的证明参考这篇题解)
设查询点的序列为 \(a\),未知点为 \(x\)。
我们当前已经知道了所有的 \(dis(a_i,x),dis(a_i,u)\),设 \(d_i=dis(a_i,x) - dis(a_i,u)\)
别问我怎么想出来要算这个东西的,我不知道QAQ,膜拜大佬就完事了
记 \(LCA(a_i,x)\) 为 \(w\),显然有 \(d_i=dis(a_i,x)-dis(a_i,u)=dis(w,x)-dis(w,u)\)
下面分情况讨论:
-
若 \(x\) 和 \(a_i\) 在 \(u\) 的不同子树中,则 \(w=u,d_i=dis(x,u)\)
-
若 \(x\) 和 \(a_i\) 在 \(u\) 的同一棵子树中,则显然 \(dis(w,x) \lt dis(u,x),dis(w,u)<dis(u,x)\),故 \(d_i=dis(w,x)-dis(w,u)\lt dis(u,x)\)
综上,如果 \(d_i\) 全相等,说明 \(d_i\) 全为 \(dis(u,x)\),则 \(x\) 在唯一没有查询点的子树里。
反之,只需找出最小的 \(d_i\),那么 \(a_i\) 和 \(x\) 在同一棵子树里。
将 \(u\) 转移到这棵子树,继续递归套娃,就能找到 \(x\) 的位置。
那么现在,我们知道了这个方法的正确性,就要回归到算法设计上了。
注意到如果每次指定点 \(u\) 再 DFS 是 \(O(N^2)\) 的,这种方法仅适用于 Easy Version
。
下面的思路就比较神奇了:如果一个点 \(u\) 度数不小于 \(3\),那么只要我们指定 \(u\) 为根节点,\(u\) 就可以直接确定,不用将 \(u\) 设为查询点。又因整个树结构固定,换根也不会对子树产生影响,所以我们就选择这样的点 \(u\) 作为根节点即可。
但如果所有点的度数都 \(\leq 2\),那么这就是一条链,直接输出 \(1\) 就行了。
时间复杂度:\(O(N)\)。
Code
#include <bits/stdc++.h>
#include <algorithm>
#define pb emplace_back
using namespace std;
const int maxn = 2e5 + 5;
vector<int> g[maxn];
int n;
int dfs(int u,int fa) {
int sz = 0,x = 0;
for(auto v : g[u]) {
if(v == fa)continue ;
int t = dfs(v , u);
sz += t;
x += !t;
}
return sz + max(x - 1 , 0);
}
void work() {
scanf("%d",&n);
for(int i = 1;i <= n;++ i)g[i].clear();
for(int i = 1;i < n;++ i) {
int x,y;
scanf("%d%d",&x,&y);
g[x].pb(y);
g[y].pb(x);
}
int deg = 0;
for(int i = 1;i <= n;++ i)deg = max(deg , (int)g[i].size());
if(!deg) {
puts("0");
return ;
}
else if(deg < 3) {
puts("1");
return ;
}
else {
for(int i = 1;i <= n;++ i) {
if(g[i].size() >= 3) {
printf("%d\n",dfs(i , i));
break ;
}
}
}//我也不知道为啥按照官方题解写法就对,换自己的写法就错QAQ
return ;
}
int main() {
int T;
scanf("%d",&T);
while(T --)work();
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿