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

洛谷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\),那么请你输出:

\[ \frac{h_1+h_2+\ldots+h_K}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] + 1= dfn[i+1]} \Rightarrow \mathtt{dfn[i] < dfn[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 !
评论
  目录