[洛谷 P4099 HEOI2013] SAO (树形dp求方案数)

[洛谷 P4099 HEOI2013] SAO (树形dp求方案数)洛谷 P4099HEOI201 SAO 题目链接大致题意 给出一个树形图 求有多少种不同的拓扑序解题思路 如果你把这题看成拓扑图 多半你已经凉了 树形图 那么就和树没有什么区别 我们完全可以把它看作是一棵树 然后考虑方向的限制通常我们在树上跑 dpdpdp 就是在做树形 dpdpdp 设 f i f i f i 表示以 iii 为根的子树不同的拓扑方案数然而在转移的过程中可以发现 转移的序列是这样子的 xxxxxxUxxxxx 99sao 在线

[洛谷 P4099 HEOI2013] SAO


题目链接


大致题意:

给出一个树形图,求有多少种不同的拓扑序


解题思路:

如果你把这题看成拓扑图,多半你已经凉了……

树形图,那么就和树没有什么区别,我们完全可以把它看作是一棵树,然后考虑方向的限制

通常我们在树上跑 d p dp dp,就是在做树形 d p dp dp,设 f [ i ] f[i] f[i]表示以 i i i为根的子树不同的拓扑方案数

然而在转移的过程中可以发现,转移的序列是这样子的: x x x x x x U x x x x x x V x x x x x x xxxxxxUxxxxxxVxxxxxx xxxxxxUxxxxxxVxxxxxx,也就是说我们无法确定序列到底是什么样的,这时,我们需要在增加一维,帮助我们确定序列,我们加一维 i i i在拓扑序列中的排名,这样我们就可以准确得知拓扑序列的样子

最终确定转移方程为: f [ i ] [ j ] f[i][j] f[i][j]表示i在子树 i i i中拓扑序排名为 j j j的方案数

转移让人头疼啊

s z [ i ] sz[i] sz[i]表示 i i i子树的节点数

假设现在我们有两个树 u , v , u u,v,u u,v,u是父亲(也就是说 v v v其实是 u u u的一个子树,只是还没转移) u u u的拓扑序小于 v v v的拓扑序(假设条件,即 u < v u<v u<v),我们讨论从 f [ u ] [ p 1 ] f[u][p1] f[u][p1] f [ v ] [ p 2 ] f[v][p2] f[v][p2]转移得到新的 f [ u ] [ p 3 ] f[u][p3] f[u][p3],转移前, u u u在原序列排名为 p 1 p1 p1, v v v在原序列排名为 p 2 p2 p2,转移后, u u u在新序列排名为 p 3 p3 p3, v v v在新序列排名为 p 4 p4 p4,且 p 3 < p 4 p3<p4 p3<p4

那么在 u u u的子树中, p 1 p1 p1左边的点一定在 p 3 p3 p3的左边(因为都同 u u u比较)

又因为 p 3 < p 4 p3<p4 p3<p4,那么 v v v原序列中 [ p 2 , s z y ] [p2,sz_y] [p2,szy]都在 p 3 p3 p3右边, [ 1 , p 2 − 1 ] [1,p2-1] [1,p21]有一些可以在 p 3 p3 p3左边,有一些可以在 p 3 p3 p3右边

得出 p 3 p3 p3左边序列数的数量限制为: p 1 − 1 < = p 3 − 1 < = p 1 − 1 + p 2 − 1 p1-1<=p3-1<=p1-1+p2-1 p11<=p31<=p11+p21,即 p 1 < = p 3 < = p 1 + p 2 − 1 p1<=p3<=p1+p2-1 p1<=p3<=p1+p21

接下来考虑 p 3 p3 p3左边的情况,左边有 p 3 − 1 p3-1 p31个点,一定有 p 1 − 1 p1-1 p11个点来自 u u u的原序列,所以左边的方案数为 C p 3 − 1 p 1 − 1 C_{p3-1}^{p1-1} Cp31p11,右边情况,有 s z u + s z v − p 3 sz_u+sz_v-p3 szu+szvp3个点,一定有 s z u − p 1 sz_u-p1 szup1个点来自 u u u的原序列,所以右边的方案数为 C s z u + s z v − p 3 s z u − p 1 C_{sz_u+sz_v-p3}^{sz_u-p1} Cszu+szvp3szup1

综上,从 f [ u ] [ p 1 ] f[u][p1] f[u][p1], f [ v ] [ p 2 ] f[v][p2] f[v][p2]转移到新的 f [ u ] [ p 3 ] f[u][p3] f[u][p3],而且新序列中 u u u v v v左边,要满足,转移方程 p 1 < = p 3 < = p 1 + p 2 − 1 p1<=p3<=p1+p2-1 p1<=p3<=p1+p21

转移方程: f [ u ] [ p 3 ] + = f [ u ] [ p 1 ] f [ v ] [ p 2 ] C p 3 − 1 p 1 − 1 C s z u + s z v − p 3 s z u − p 1 f[u][p3]+=f[u][p1]f[v][p2]C_{p3-1}^{p1-1}C_{sz_u+sz_v-p3}^{sz_u-p1} f[u][p3]+=f[u][p1]f[v][p2]Cp31p11Cszu+szvp3szup1

另一种情况是 v v v的拓扑序小于 u u u的拓扑序,推导过程同理, p 3 p3 p3的取值范围发生了变化 p 1 + p 2 < = p 3 < p 1 + s z u p1+p2<=p3<p1+sz_u p1+p2<=p3<p1+szu

枚举 p 1 , p 2 , p 3 p1,p2,p3 p1,p2,p3时间复杂度是 O ( n 3 ) O(n^3) O(n3),考虑优化

for(int p1=1;p1<=sz[u];++p1) for(int p2=1;p2<=sz[v];++p2) for(int p3=p1;p3<=sz[u]+sz[v]-1) 转移方程 

p 2 , p 3 调 换 顺 序 p2,p3调换顺序 p2,p3

for(int p1=1;p1<=sz[u];++p1) for(int p3=p1;p3<=p1+sz[v]-1;++p3) for(int p2=p3-p1+1;p3<=sz[v]) 转移方程 

可以把 p 2 p2 p2循环省掉,利用前缀和优化,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

头都大了


AC代码:

#include <bits/stdc++.h> #define rep(i, n) for (int i = 1; i <= (n); ++i) using namespace std; typedef pair<int, int> PII; typedef long long ll; const int N = 1010, mod = 1e9 + 7; int n, m; ll f[N][N], dp[N]; //f[i][j]表示i点在i的子树节点中的序列排第j个的方案数 dp[i]表示f[i]的前缀和 int sz[N], c[N][N]; vector<pair<int, int>>e[N]; void dfs(int u, int fa) { 
    //初始化 sz[u] = 1; f[u][1] = 1; for (auto& op : e[u]) { 
    int v = op.first, w = op.second; if (v == fa)continue; dfs(v, u); memcpy(dp, f[u], sizeof dp); memset(f[u], 0, sizeof f[u]); if (w == 1) { 
    for (int p1 = 1; p1 <= sz[u]; ++p1) for (int p3 = p1; p3 < p1 + sz[v]; ++p3) f[u][p3] = (f[u][p3] + 1ll * c[sz[u] + sz[v] - p3][sz[u] - p1] * c[p3 - 1][p1 - 1] % mod * dp[p1] % mod * (f[v][sz[v]] - f[v][p3 - p1] + mod)) % mod; } else { 
    for (int p1 = 1; p1 <= sz[u]; ++p1) for (int p3 = p1 + 1; p3 <= p1 + sz[v]; ++p3) f[u][p3] = (f[u][p3] + 1ll * c[sz[u] + sz[v] - p3][sz[u] - p1] * c[p3 - 1][p1 - 1] % mod * dp[p1] % mod * f[v][p3 - p1]) % mod; } //一定在转移方程的后面 sz[u] += sz[v]; } //前缀和 for (int i = 1; i <= sz[u]; ++i) f[u][i] = (f[u][i] + f[u][i - 1]) % mod; } int main(void) { 
    //预处理组合数 c[0][0] = 1; for (int i = 1; i < N; ++i) { 
    c[i][0] = 1; for (int j = 1; j <= i; ++j) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; } int t; cin >> t; while (t--) { 
    int n; cin >> n; //初始化 for (int i = 1; i <= n; ++i)e[i].clear(); memset(sz, 0, sizeof sz); int a, b; char c; for (int i = 1; i < n; ++i) { 
    cin >> a >> c >> b; ++a; ++b; e[a].push_back({ 
    b,c == '<' }); e[b].push_back({ 
    a,c == '>' }); } dfs(1, -1); cout << f[1][n] << endl; } return 0; } 
今天的文章 [洛谷 P4099 HEOI2013] SAO (树形dp求方案数)分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2025-01-02 21:40
下一篇 2025-01-02 21:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/99445.html