[CF1695D]Tree Queries 题解

传送门QAQ

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;
}

完结撒花✿✿ヽ(°▽°)ノ✿

posted @ 2022-06-28 21:38  ImALAS  阅读(37)  评论(0编辑  收藏  举报