嘘~ 正在从服务器偷取页面 . . .

CF1098C Construct a tree 题解


CF1098C Construct a tree 题解

题目链接:Construct a tree

题意

Misha 在雪地森林中漫步,他对树木如此着迷,以至于决定自己绘制一棵树!

Misha 想构造一棵有 \(n\) 个顶点的有根树,顶点从 \(1\)\(n\) 编号,其中根的编号为 \(1\)。每个其他顶点都有一个父节点 \(p_i\),且 \(i\) 是顶点 \(p_i\) 的子节点。顶点 \(u\) 属于顶点 \(v\) 的子树,当且仅当从 \(u\) 开始通过父节点(\(u\), \(p_{u}\), \(p_{p_{u}}\), ...)可以到达 \(v\)。显然,\(v\) 属于它自己的子树,子树中的顶点数量称为子树的大小。Misha 只对每个顶点都属于顶点 1 的子树的树感兴趣。

下面是一个有 6 个顶点的树。顶点 2 的子树包含顶点 2、3、4、5。因此其子树的大小是 4。

树的分支系数定义为任意顶点的最大子节点数。例如,上述树的分支系数为 2。你的任务是构造一棵有 \(n\) 个顶点的树,使所有顶点的子树大小之和等于 \(s\),并且分支系数尽可能小。

输入格式

唯一的输入行,包含两个整数 \(n\)\(s\)

树的顶点数量和所需的子树大小之和(\(2 \le n \le 10^5\)\(1 \le s \le 10^{10}\))。

输出格式

如果所需的树不存在,则输出 No

否则在第一行输出 Yes,接下来一行输出整数 \(p_2\)\(p_3\),...,\(p_n\),其中 \(p_i\) 表示顶点 \(i\) 的父节点。

这道题类似于 CF1311E Construct the Binary Tree

首先子树大小的和可以转化为每个节点的深度的和。

因为要求度数最小的方案,考虑二分最大度数 \(k\) 。显然度数越小,子树和越大。

由于深度最小的完全 \(k\) 叉树的深度和最小,所以对于合法的 \(k\) ,我们可以从最小的情况扩展。

考虑记录每个深度的节点的个数,以及每个节点的深度。考虑从深度最深的节点开始扩展即可。

时间复杂度 \(\mathcal{O}(n \log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(1e6 + 15))

int n, k, d[N], cnt[N];
bool check(int x)
{
    int tmp = 1, dep = 0, num = k - n;
    rep(i, 0, n) d[i] = cnt[i] = 0;
    for(int i = 2; i <= n; )
    {
        tmp *= x; ++dep;
        for(int j = 1; j <= tmp && i <= n; i++, j++) {
            ++cnt[dep]; d[i] = dep; num -= dep;
        }
        if(num < 0) return false;
    }
    for(int j = n; num; )
    {
        ++dep; if(cnt[d[j]] == 1) --j;
        down(tmp = num, dep - d[j]);
        --cnt[d[j]]; d[j] += tmp;
        ++cnt[d[j]]; num -= tmp; --j;
    }
    return true;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> k;
    if(k < 2 * n - 1 || k > n * (n + 1) / 2) return cout << "No\n", 0;
    int l = 1, r = n;
    while(l < r)
    {
        int mid = (l + r) >> 1;
        check(mid) ? r = mid : l = mid + 1;
    }
    check(l); cout << "Yes" << '\n';
    d[1] = 0; sort(d + 2, d + 1 + n); rep(i, 0, n) cnt[i] = 0;
    for(int i = 2, j = 1; i <= n; i++)
    {
        while(d[j] != d[i] - 1 || cnt[j] == l) ++j;
        cout << j << " \n"[i == n]; ++cnt[j];
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/lwa77mqw


题外话

放图片。

听说我的 blog 是图片网站?


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录