Codeforces 1695D. Tree Queries

传送门

Easy Version

\(\texttt{Difficulty:2200}\)

题目大意

一棵 \(n(1\le n\le2000)\) 个节点的树,有一个未知节点 \(x\) ,每次询问一个节点 \(v\) ,得到 \(dis(v,x)\) ,求最少询问几个节点,在所有情况下,都能够唯一确定 \(x\)

思路

考虑树形 \(dp\) 。设 \(f_v\) 为在 \(v\) 为根的子树中,当 \(v\) 的向上子树已经有节点被询问的情况下,确定任意一点所需的最少查询数,记 \(cnt_v\)\(v\) 的儿子中 \(f=0\) 的数量,我们当前仅需要能够确定节点在哪个子树中即可,因为我们进入任意的子树后都可以去确定节点。当 \(cnt>2\) 时,显然无法确定,否则一定可以确定,因为如果节点 \(x\) 在没有询问的子树当中,对任意其他子树中被询问的点为 \(y\) ,记 \(v\) 向上子树中被询问的一个点为 \(z\) ,于是一定有 \(dis(z,x)+dis(z,y)-2\cdot dis(z,v)\) ,否则存在不满足(\(=\) 变为 \(>\))的点。那么我们如果 \(cnt_v\) 过大,我们对没有询问的子树补上一个询问节点即可,于是可以得出转移方程:

\[f_v=\sum_{u\in son[v]}f_u+max(cnt_v-1,0) \]

此外叶节点的 \(f=0\),最后答案为 \(f_root+1\) 通过强制选取跟来保证各个节点满足状态定义,由于根对答案有影响,要枚举根取最小值,复杂度 \(O(n^2)\)

代码

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mk make_pair
//#define int LL
//#define lc P*2
//#define rc P*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000009;
const LL mod = 998244353;
const int maxn = 200010;

int T, N, f[maxn];
vector<int>G[maxn];

void add_edge(int from, int to)
{
    G[from].push_back(to);
    G[to].push_back(from);
}

void dfs(int v, int p)
{
    int cnt = 0;
    for (auto& to : G[v])
    {
        if (to == p)
            continue;
        dfs(to, v);
        if (!f[to])
            cnt++;
        f[v] += f[to];
    }
    f[v] += max(cnt - 1, 0);
}

void solve()
{
    if (N == 1)
    {
        cout << 0 << endl;
        return;
    }
    int ans = N;
    for (int i = 1; i <= N; i++)
    {
        dfs(i, 0), ans = min(ans, f[i] + 1);
        for (int j = 1; j <= N; j++)
            f[j] = 0;
    }
    cout << ans << endl;
}

int main()
{
    IOS;
    cin >> T;
    while (T--)
    {
        cin >> N;
        for (int i = 1; i <= N; i++)
            G[i].clear();
        int x, y;
        for (int i = 1; i < N; i++)
            cin >> x >> y, add_edge(x, y);
        solve();
    }

    return 0;
}

Hard Version

\(\texttt{Difficulty:2300}\)
\(\texttt{Easy Version}\) 唯一的区别是 \(1\le n\le2\cdot10^5\)

思路

在简单版本的做法中,我们为了保证状态的定义,强制选取了根,但我们也可以不选取根,如果一个节点的度 \(>2\) ,那么以该节点为根时,其至少两个儿子的子树内有节点被选,于是可以保证整棵树的任意一个节点的向上子树中都有节点被选,于是该根可以不用被选取,于是我们找一个这样的跟,进行一遍简单版本的 \(dp\) 即为答案,注意此时最后的答案不需要 \(+1\) ,此外如果没有这样的根,说明为一条链,答案为 \(1\)

代码

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mk make_pair
//#define int LL
//#define lc P*2
//#define rc P*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000009;
const LL mod = 998244353;
const int maxn = 200010;

int T, N, f[maxn], deg[maxn];
vector<int>G[maxn];

void add_edge(int from, int to)
{
    G[from].push_back(to);
    G[to].push_back(from);
}

void dfs(int v, int p)
{
    int cnt = 0;
    for (auto& to : G[v])
    {
        if (to == p)
            continue;
        dfs(to, v);
        if (!f[to])
            cnt++;
        f[v] += f[to];
    }
    f[v] += max(cnt - 1, 0);
}

void solve()
{
    if (N == 1)
    {
        cout << 0 << endl;
        return;
    }
    int root = 0;
    for (int i = 1; i <= N; i++)
    {
        if (deg[i] > 2)
        {
            root = i;
            break;
        }
    }
    if (!root)
    {
        cout << 1 << endl;
        return;
    }
    dfs(root, 0);
    cout << f[root] << endl;
}

int main()
{
    IOS;
    cin >> T;
    while (T--)
    {
        cin >> N;
        for (int i = 1; i <= N; i++)
            G[i].clear(), deg[i] = 0, f[i] = 0;
        int x, y;
        for (int i = 1; i < N; i++)
            cin >> x >> y, add_edge(x, y), deg[x]++, deg[y]++;
        solve();
    }

    return 0;
}
posted @ 2022-07-02 23:10  Prgl  阅读(86)  评论(0)    收藏  举报