洛谷P4099 [HEOI2013]SAO 题解

本文详细解析了洛谷P4099题目的解题思路,该题目涉及树形动态规划(Tree DP)和拓扑序的概念。作者通过分析题目给出的限制条件,提出从树形DP的角度解决问题,并介绍了如何利用子树合并的两种情况来计算拓扑序的总数。文章还提供了关键的转移方程和代码实现,并提到了前缀和优化技巧。此外,文章给出了组合数的预处理方法,以及多组数据处理的注意事项。
摘要由CSDN通过智能技术生成

洛谷P4099 [HEOI2013]SAO 题解

题目链接:P4099 [HEOI2013]SAO

题意

Welcome to SAO ( Strange and Abnormal Online)。这是一个 VR MMORPG, 含有 n n n 个关卡。但是,挑战不同关卡的顺序是一个很大的问题。

某款游戏有 n − 1 n-1 n1 个对于挑战关卡的限制,诸如第 i i i 个关卡必须在第 j j j 个关卡前挑战,或者完成了第 k k k 个关卡才能挑战第 l l l 个关卡。并且,如果不考虑限制的方向性,那么在这 n − 1 n-1 n1 个限制的情况下,任何两个关卡都存在某种程度的关联性。即,我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任何限制。

高质量好题👍 做了我一下午+晚上

首先这个题面的意思:给定一棵有方向的树,求拓扑序总数

注意到这个特殊的性质,可以从树形dp的角度去解决这个问题

d p [ u ] [ i ] dp[u][i] dp[u][i] 表示结点 u u u 在其子树的拓扑序中位于第 i i i 位的方案数

对于每个 u u u 的儿子 v v v ,将 v v v 不断合并到 u u u 上,则有两种情况

  • 新拓扑序中 v v v u u u

    考虑枚举一个 j j j 表示 v v v 的子树中有 j j j 个结点合并到了 u u u 的前面

    则新的状态为 d p [ u ] [ i + j ] dp[u][i+j] dp[u][i+j]

    • 合并后 u u u 前面有 i + j − 1 i+j-1 i+j1 个元素,

      其中 j j j 个是 v v v 的,所以 i + j − 1 i+j-1 i+j1 个格子取 j j j 个,则为 ( i + j − 1 j ) \dbinom{i+j-1}{j} (ji+j1)

    • 同理, u u u 后面有 sz [ u ] + sz [ v ] − i − j \text{sz}[u]+\text{sz}[v]-i-j sz[u]+sz[v]ij 个元素,

      其中 sz [ v ] − j \text{sz}[v]-j sz[v]j 个是 v v v 的,则为 ( sz [ u ] + sz [ v ] − i − j sz [ v ] − j ) \dbinom{\text{sz}[u]+\text{sz}[v]-i-j}{\text{sz}[v]-j} (sz[v]jsz[u]+sz[v]ij)

    故转移方程为
    d p [ u ] [ i + j ] = d p [ u ] [ i + j ] + ( i + j − 1 j ) × ( sz [ u ] + sz [ v ] − i − j sz [ v ] − j ) × d p [ u ] [ i ] × d p [ v ] [ k ] dp[u][i+j]=dp[u][i+j]+\dbinom{i+j-1}{j}\times \dbinom{\text{sz}[u]+\text{sz}[v]-i-j}{\text{sz}[v]-j} \times dp[u][i]\times dp[v][k] dp[u][i+j]=dp[u][i+j]+(ji+j1)×(sz[v]jsz[u]+sz[v]ij)×dp[u][i]×dp[v][k]
    也就是

    for (int i=1;i<=sz[u];++i)
        for (int k=1;j<=sz[v];++k)
            for (int j=k;j<=sz[v];++j)
                dp[u][i+j]+=dp[u][i]*dp[v][k]*C[i+j-1][i-1]*C[sz[u]+sz[v]-i-j][sz[u]-i]);
    

    考虑变换一下 j , k j,k j,k 的枚举顺序

    for (int i=1;i<=sz[u];++i)
        for (int j=1;j<=sz[v];++j)
            for (int k=1;k<=j;++k)
                dp[u][i+j]+=dp[u][i]*dp[v][k]*C[i+j-1][i-1]*C[sz[u]+sz[v]-i-j][sz[u]-i];
    

    然后就可以愉快的前缀和优化啦!

  • 新拓扑序中 v v v u u u

    与上一种情况类似

    for (int i=1;i<=sz[u];++i)
        for (int j=0;j<=sz[v];++j)
            for (int k=i+1;k<=sz[v];++k)
                dp[u][i+j]+=dp[u][i]*dp[v][k]*C[i+j-1][i-1]*C[sz[u]+sz[v]-i-j][sz[u]-i];
    

    也可以前缀和优化

然后组合数 O ( n 2 ) O(n^2) O(n2) 预处理

注意由于 dp[u][i+j]的更新,我们需要记录临时记录旧的 dp[u][i]

所以总的时间复杂度为 O ( n 2 ) O(n^2) O(n2) (类似于树上背包的复杂度证明,此处略)

别忘了多组数据哦! qwq

代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e3+15)
const int p=1e9+7;
struct Edge
{
    int u,v,w,next;
}e[N<<1];
int pos=1,head[N],C[N][N];
int n,sz[N],dp[N][N],tmp[N];
void addEdge(int u,int v,int w)
{
    e[++pos]={u,v,w,head[u]};
    head[u]=pos;
}
void clear()
{
    pos=1;
    for(int i=1; i<=n; i++)
    {
        head[i]=sz[i]=0;
        for(int j=1; j<=n; j++)
            dp[i][j]=0;
    }
}
void dfs(int u,int f)
{
    sz[u]=1;dp[u][1]=1;
    for(int i=head[u]; i; i=e[i].next)
    {
        int v=e[i].v;
        if(v==f)continue;
        dfs(v,u);
        for(int i=1; i<=sz[u]; i++)
            tmp[i]=dp[u][i],dp[u][i]=0;
        if(e[i].w)
        {
            for(int i=1; i<=sz[u]; i++)
                for(int j=0; j<sz[v]; j++)
                {
                    dp[u][i+j]=(dp[u][i+j]+C[sz[u]+sz[v]-i-j][sz[v]-j]*
                    C[i+j-1][j]%p*tmp[i]%p*(dp[v][sz[v]]-dp[v][j]+p)%p)%p;
                }
        }else
        {
            for(int i=1; i<=sz[u]; i++)
                for(int j=1; j<=sz[v]; j++)
                {
                    dp[u][i+j]=(dp[u][i+j]+C[sz[u]+sz[v]-i-j][sz[v]-j]*
                    C[i+j-1][j]%p*tmp[i]%p*dp[v][j]%p)%p;
                }
        }
        sz[u]+=sz[v];
    }
    for (int i=1; i<=sz[u]; i++)
        dp[u][i]=(dp[u][i]+dp[u][i-1])%p;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    C[0][0]=1;
    for(int i=1; i<=N-10; i++)
    {
        C[i][0]=1;
        for(int j=1; j<=i; j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
    }
    int Q;cin >> Q;
    while(Q--)
    {
        clear();
        cin >> n;
        for(int i=1,u,v; i<n; i++)
        {
            char ch;cin >> u >> ch >> v;
            ++u;++v;
            addEdge(u,v,ch=='<');
            addEdge(v,u,ch=='>');
        }
        dfs(1,1);
        cout << dp[1][n] << endl;
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/blog/i-am-zhiyangfan/solution-p4099

[2] https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p4099

[3] https://m-sea.blog.luogu.org/solution-p4099

转载请说明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值