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

洛谷P1232 [NOI2013] 树的计数 题解


洛谷P1232 [NOI2013] 树的计数 题解

题目链接:P1232 [NOI2013] 树的计数

题意

我们知道一棵有根树可以进行深度优先遍历(DFS)以及广度优先遍历(BFS)来生成这棵树的 DFS 序以及 BFS 序。两棵不同的树的 DFS 序有可能相同,并且它们的 BFS 序也有可能相同,例如下面两棵树的 DFS 序都是 1 2 4 5 3,BFS 序都是 1 2 3 4 5

现给定一个 DFS 序和 BFS 序,我们想要知道,符合条件的有根树中,树的高度的平均值。即,假如共有 $K$ 棵不同的有根树具有这组 DFS 序和 BFS 序,且他们的高度分别是 $h_1, h_2, \ldots, h_K$,那么请你输出:

输入格式

第一行包含 $1$ 个正整数 $n$,表示树的节点个数。

第二行包含 $n$ 个正整数,是一个 $1 \ldots n$ 的排列,表示树的 DFS 序。

第三行包含 $n$ 个正整数,是一个 $1 \ldots n$ 的排列,表示树的 BFS 序。

输入保证至少存在一棵树符合给定的两个序列。

输出格式

输出 $1$ 个实数,四舍五入保留恰好三位小数,表示树高的平均值。

数据范围

对于 $100\%$ 的测试数据,满足:$2 \le n \le 2 \times 10^5$。

考虑对 $\mathtt{BFS}$ 序进行分段。

但题目直接给了个 $\mathtt{DFS}$ 序和 $\mathtt{BFS}$ 序着实不太好做,考虑结点按 $\mathtt{BFS}$ 序重新编号。

此时的 $\mathtt{dfn[]}$ 数组就表示 $\mathtt{BFS}$ 序为 $1,2,\cdots,n$ 时的 $\mathtt{DFS}$ 序。

并且显然有 $\mathtt{bfn[i]=i}$ ,因此我们不需要这个 $\mathtt{bfn[]}$ 了。

我们可以用下面的代码来实现这个转换(下文的结点编号默认为转换后的编号。)

for(int i=1,x; i<=n; i++) { cin >> x; dfn[x] = i; } // 这里临时用于标号
for(int i=1,x; i<=n; i++) { cin >> x; id[dfn[x]] = i; }
for(int i=1; i<=n; i++) dfn[id[i]] = i; // 转换完成


考虑 $\mathtt{BFS}$ 序下连续的两点(即 $i,i+1$ )什么时候一定分段

首先 $1$ 肯定要分段,因为根结点只有一个。

若 $\mathtt{dep[i] = dep[i+1]}$,则

因此若 $\mathtt{dfn[i] > dfn[i+1]}$ ,$i$ 后一定要分段。( $\mathtt{dfn[i]}$ 不可能等于 $\mathtt{dfn[i+1]}$ )

注意到 $\mathtt{dep[i]=dep[i+1]}$ 时, $i$ 与 $i+1$ 在 $\mathtt{DFS}$ 序中也是连续的。

接着考虑对 $\mathtt{DFS}$ 序连续的两点进行讨论,考虑他们在 $\mathtt{BFS}$ 序下的关系

设点 $i,j$ 在 $\mathtt{DFS}$ 序下连续,即 $\mathtt{dfn[i] + 1 =dfn[j]}$ 。

  • 当 $i = j-1$ 时,我们无法确定 $i$ 是否需要分层。

    因为 $j$ 可能是 $i$ 的儿子,也可能是 $i$ 的后继兄弟。

  • 当 $i > j-1$ 时,$j$ 一定是 $i$ 的某个祖先的后继兄弟

  • 当 $i < j-1$ 时,$j$ 一定是 $i$ 的第一个儿子。

    此时,区间 $[i,j-1]$ 一定存在一个分段点

    考虑下图(注意 $i+1,j-2$ 这种不一定存在,只是画了一下)

考虑用差分维护每个点能否作为分段点

  • 若一个点必须成为分段点,则对答案的贡献为 $1$ 。
  • 若一个点不能被分段,则对答案的贡献为 $0$ 。
  • 若一个点没有限制,由于每个点是否分段是相互独立的,所以贡献为 $\frac{1}{2}$ 。

时间复杂度 $\mathcal{O}(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 N ((int)(2e5+15))

int n,id[N],dfn[N],sum[N];
void put(int l,int r) { ++sum[l]; --sum[r+1]; }
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; double ans = 1; put(1,1); // 根节点要分一段
    for(int i=1,x; i<=n; i++) { cin >> x; dfn[x] = i; }
    for(int i=1,x; i<=n; i++) { cin >> x; id[dfn[x]] = i; }
    for(int i=1; i<=n; i++) dfn[id[i]] = i;
    for(int i=1; i<n; i++)
    {
        if(dfn[i] > dfn[i+1]) { ++ans, put(i,i); } // 这里也要标记,去掉0.5的贡献
        if(id[i] < id[i+1] - 1) put(id[i], id[i+1]-1); // 标记,在后面计算0.5的贡献
    }
    for(int i=1; i<n; i++) { ans += (sum[i] += sum[i-1]) ? 0 : 0.5; }
    cout << fixed << setprecision(3) << ans + 1 << '\n';
    return 0;
}

本文累计耗时 8h 完成,我真服了。点个赞吧,虽然我好像还没有点赞功能


参考文献

[1] https://www.luogu.com.cn/blog/user34274/solution-p1232

[2] https://www.luogu.com.cn/blog/CXY07/solution-p1232


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