【算法题解】拓扑序计数+树形DP

简介: 【算法题解】拓扑序计数+树形DP

拓扑序计数+树形DP

题目

链接https://ac.nowcoder.com/acm/contest/38630/F

image-20220904192158381

思路

每个公司是一棵树,有n家公司,可以将这n家公司连到一个虚拟的根上。总共的排队方案就等于这个棵的排队方案树。为了满足排队是顺序的,所以我们要求的就是这棵树的拓扑序个数。用树形DP来求解。

f[u]: 以u为根的子树的拓扑序数

sz[u]: 以u为根的子树的大小(节点的数量)

如何计算一个棵的拓扑序数?

我们先来看只有两个子树的情况:

image-20220904195600066

如上图所示,1号节点一定是第1为,那么剩下还有3位置,有三种可能:

  • 1 2 3 4
  • 1 3 2 4
  • 1 2 4 3

这就用到了概率论的知识,有三个盒子,从中要选2个盒子给2、4放置,并且是按照顺序的2 4放置,因为是拓扑序,所以每个子树的相对顺序是一定的,所以最后计算的结果就是:

$$ f[u]=f[v1]⋅f[v2]⋅C(sz[v1]+sz[v2],sz[v1]) $$

当树为二叉树时,将两个子树v1,v2进行合并:即先把各子树的方案数乘起来算出总方案,然后考虑各子树元素的相对排列顺序,即在总的节点个数中选sz[v1]sz[v1]个位置,剩下的顺序就固定了,保证每颗子树的相对拓扑序不变。

image-20220904200620793

如何计算组合数

在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef priority_queue<int, vector<int>, less<int>> Q;
#define x first
#define y second
#define endl '\n'
#define ppb pop_back
#define pb push_back
#define pf push_front
#define YES cout << "YES" << endl
#define Yes cout << "Yes" << endl
#define yes cout << "yes" << endl
#define NO cout << "NO" << endl
#define No cout << "No" << endl
#define no cout << "no" << endl
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof(x))
#define rep(i, l, r) for (LL i = l; i <= (r); ++i)
#define per(i, r, l) for (LL i = r; i >= (l); --i)
const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 1e9 + 7;
vector<int> v[N];
int n;
int sz[N];
ll fac[N];
ll inv[N];
ll f[N];
ll qsm(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void dfs(int u)
{
    f[u] = 1;
    sz[u] = 1;
    for (int i = 0; i < v[u].size(); i++)
    {
        int j = v[u][i];
        dfs(j);
        sz[u] += sz[j];
        f[u] = (f[u] * f[j]) % mod * inv[sz[j]] % mod;
    }
    f[u] = f[u] * fac[sz[u] - 1] % mod;
}
void solve()
{
    fac[0] = 1;
    inv[0] = 1;
    for (int i = 1; i < N; i++)
    {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = inv[i - 1] * qsm(i, mod - 2) % mod;
    }
    cin >> n;
    ll ans = 1;
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        int c;
        cin >> c;
        v[0].pb(cnt + 1);
        for (int j = 2; j <= c; j++)
        {
            int u;
            cin >> u;
            v[cnt + u].pb(cnt + j);
        }
        cnt += c;
    }
    dfs(0);
    cout << f[0];
}
signed main()
{
#ifdef Xin
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    int T = 1;
    while (T--)
        solve();
    return 0;
}

感谢大佬的文章:

相关文章
|
3月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
103 1
|
17天前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
22 2
|
1月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
24 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
58 2
|
1月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
107 0
|
1月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
27 0
|
1月前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
17 0
|
1月前
|
算法 C++
【算法】DP背包问题(C/C++)
【算法】DP背包问题(C/C++)
|
1月前
|
人工智能 算法 BI
【算法】 线性DP(C/C++)
【算法】 线性DP(C/C++)
下一篇
无影云桌面