JZOJ5418. 【NOIP2017提高A组集训10.24】 树形DP+组合数

一道NOIP2017提高组的题目,要求根据特定条件排列数的顺序。由于每个人最多一个要求,形成的图是森林结构。解决方案是使用树形DP和组合数,自底向上遍历每棵树,计算以每个节点为根的排列方案数,并最终乘以节点的子树数量,得到总方案数。这表明部分分策略不一定是暴力解的优化版。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出n个数,有一些特定要求,形如x,y表示x一定要在y的左边,求排列方案数。

一早上死想序列做法,想到了一下连边然后否决了。。然后想出了50分的容斥或者状压,然后觉得100分是不是再优化一下啥的,然后就再没脱出坑。。
事实证明部分分做法不一定是正解做法的暴力版本。。
首先有一个条件就是每一个人最多提出一个要求。
那么连边后的dag是一个森林,不相交的n棵树。
那么对于每一棵树,我们从下往上扫,记录当前走过的点数,设为sz,那么有:
g[x]=g[v]C(size[x],size[v])
表示以x为根的方案数,这个用乘法原理很好理解,每一颗子树是独立的。
然后答案也是一样的,相当于有一个超级根把所有树连在一起,所以就有:
ans=g[i]C(sz,size[i]),同理,也是独立的,直接乘就好。
看来思维不能太固定了= =。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=2e5+5;
int head[N],next[N],go[N],size[N],d[N];
int tot,fac[N],inv[N],n,m,mo,f[N],g[N];
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline int find(int x)
{
    if (f[x]==x) return x;
    else return f[x]=find(f[x]);
}
inline int C(int n,int m)
{
    return 1ll*fac[n]*inv[m]%mo*inv[n-m]%mo;
}
inline void dfs(int x)
{
    g[x]=1;size[x]=0;
    for (int i=head[x];i;i=next[i])
    {
        int v=go[i];
        dfs(v);
        size[x]+=size[v];
        g[x]=1ll*g[x]*g[v]%mo*C(size[x],size[v])%mo;
    }
    size[x]++;
}
int main()
{
    freopen("photo.in","r",stdin);freopen("photo.out","w",stdout);
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d%d",&n,&m,&mo);
        fac[0]=inv[0]=fac[1]=inv[1]=1;
        fo(i,2,n)
        fac[i]=1ll*fac[i-1]*i%mo,inv[i]=1ll*(mo-mo/i)*inv[mo%i]%mo;
        fo(i,2,n)
        inv[i]=1ll*inv[i]*inv[i-1]%mo;
        fo(i,1,n)f[i]=i,head[i]=d[i]=0;
        int flag=0;tot=0;
        while (m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if (find(x)!=find(y))f[find(x)]=find(y);
            else flag=1;
            add(y,x);
            d[x]++;

        }
        if (flag)
        {
            printf("0\n");
            continue;
        }
        int ans=1,sz=0;
        fo(i,1,n)
        if (!d[i])
        {
            dfs(i);
            sz+=size[i];

            ans=1ll*ans*g[i]%mo*C(sz,size[i])%mo;
        }
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值