UVA11174 Stand in a Line

本文介绍了一种使用树形动态规划(DP)的方法来解决特定类型的排列组合问题,尤其是在涉及树结构的数据集上。通过构建虚拟节点将森林转换为单一树结构,算法能够有效地计算出所有可能的排列方式,利用子树之间的独立性和排列组合原理,最终得出简洁的公式计算总方案数。

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

链接

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2115

题解

首先我建一个虚点,设其为森林中所有树的父亲,那现在就成一棵树了
f(x) f ( x ) 为以节点 x x 为根的子树的答案
那么x点本身肯定排在所有子节点的前面,现在只需考虑其子树中的点怎么排列,子树内部先排列,然后再把这些排列交错着插起来,子树之间是独立的,如果把同一棵子树上的点看作相同的点,那么这就是有重复元素的排列组合,假设子树的大小为 s(c1),s(c2)...s(ck) s ( c 1 ) , s ( c 2 ) . . . s ( c k ) ,那么

f(x)=[i=1kf(ci)][s(x)1]!ki=1s(ci)! f ( x ) = [ ∏ i = 1 k f ( c i ) ] [ s ( x ) − 1 ] ! ∏ i = 1 k s ( c i ) !

其中 k k 是子节点个数,ci表示第 i i 个儿子,s(x)表示子树大小
使用代入法,就会发现当计算 f(x) f ( x ) 时,分子上会出现一个 [s(x)1]! [ s ( x ) − 1 ] !
在计算 f(father(x)) f ( f a t h e r ( x ) ) 时分母上会出现一个 s(x)! s ( x ) !
约分之后分母上剩下一个 s(x) s ( x )
[s(root)1]! [ s ( r o o t ) − 1 ] ! 不会被约去,因为 root r o o t 没有父亲节点
这样最终答案就是
n!ni=1s(i) n ! ∏ i = 1 n s ( i )

代码

//数学题
#include <bits/stdc++.h>
#define maxn 100010
#define mod 1000000007ll
#define cl(x) memset(x,0,sizeof(x))
#define ll long long
using namespace std;
ll head[maxn], to[maxn], nex[maxn], etot, N, M, mark[maxn], sz[maxn], ans, inv[maxn];
void adde(ll a, ll b){to[++etot]=b;nex[etot]=head[a];head[a]=etot;}
ll read(ll x=0)
{
    ll c, f=1;
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-48;
    return f*x;
}
void preprocess()
{
    ll i;
    inv[1]=1;
    for(i=2;i<maxn;i++)inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
void init()
{
    ll i, a, b;
    cl(head), cl(nex), etot=0, cl(mark), cl(sz), ans=1;
    N=read(), M=read();
    for(i=1;i<=M;i++)a=read(), b=read(), adde(b,a), mark[a]=1;
    for(i=1;i<=N;i++)if(!mark[i])adde(N+1,i);
}
void dfs(ll pos)
{
    ll p;
    sz[pos]=1;
    for(p=head[pos];p;p=nex[p])
    {
        dfs(to[p]);
        sz[pos]+=sz[to[p]];
    }
    if(pos!=N+1)ans=ans*inv[sz[pos]]%mod;
}
void show()
{
    ll i;
    for(i=1;i<=N;i++)ans=ans*i%mod;
    printf("%lld\n",ans);
}
int main()
{
    ll T=read();
    preprocess();
    while(T--)
    {
        init();
        dfs(N+1);
        show();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值