【CF1946E】Girl Permutation

CF1946E

题目链接

前置芝士:

树的拓扑序计数:

有根树的拓扑序计数:树形dp, d p [ u ] = ( s i z [ u ] − 1 ) ! ∗ ∏ v , v ∈ s o n [ u ] d p [ v ] s i z [ v ] ! dp[u]=(siz[u]-1)!*\prod\limits_{v,v\in son[u]} \frac{dp[v]}{siz[v]!} dp[u]=(siz[u]1)!v,vson[u]siz[v]!dp[v]

多个有根树构成的森林的拓扑序计数:只需要加上一个超级源点并与每个有根树的根结点连边即可转化为单棵有根树的问题

无根树的拓扑序计数:换根dp, d p [ u ] = d p [ u ] ∗ ( s i z [ u ] − s i z [ v ] − 1 ) ! ( s i z [ u ] − 1 ) ! ∗ s i z [ v ] ! d p [ v ] dp[u]=dp[u]*\frac{(siz[u]-siz[v]-1)!}{(siz[u]-1)!}*\frac{siz[v]!}{dp[v]} dp[u]=dp[u](siz[u]1)!(siz[u]siz[v]1)!dp[v]siz[v]!,注意这里的更新不是真正的更新,只是为了计算dp[v],实际代码用变量存结果就是了。 d p [ v ] = d p [ v ] ∗ ( n − 1 ) ! ( s i z [ v ] − 1 ) ! ∗ d p [ u ] ( s i z [ u ] − s i z [ v ] ) ! dp[v]=dp[v]*\frac{(n-1)!}{(siz[v]-1)!}*\frac{dp[u]}{(siz[u]-siz[v])!} dp[v]=dp[v](siz[v]1)!(n1)!(siz[u]siz[v])!dp[u] 在遍历 v v v前更新 s i z [ u ] = n − s i z [ v ] , s i z [ v ] = n siz[u]=n-siz[v],siz[v]=n siz[u]=nsiz[v],siz[v]=n,对 v v v跑完dfs后更新回来。

例题:F - Distributing Integers
注意一点通过费马小定理可以推得 i n v [ ( n − 1 ) ! ] = i n v [ n ! ] ∗ n inv[(n-1)!]=inv[n!]*n inv[(n1)!]=inv[n!]n

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
const int mod=1e9+7;
const int N=4e5+1000;
const db PI=acos(-1);
int n,tot,first[N],nxt[N],to[N],dp[N],siz[N],jc[N],invjc[N];
int qpow(int a,int b)
{
    int f=1;
    while(b)
    {
        if(b%2==1) f=f*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return f;
}
int inv(int x){return qpow(x,mod-2);}
void add(int a,int b)
{
    nxt[++tot]=first[a];
    first[a]=tot;
    to[tot]=b;
}
void sizdfs(int u,int fa)
{
    siz[u]=1;
    for(int i=first[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        sizdfs(v,u);
        siz[u]+=siz[v];
    }
}
void dpdfs(int u,int fa)
{
    dp[u]=jc[siz[u]-1];
    for(int i=first[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        dpdfs(v,u);
        dp[u]=(dp[u]*dp[v]%mod)*invjc[siz[v]]%mod;
    }
}
void hgdfs(int u,int fa)
{
    for(int i=first[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        int x=(((dp[u]*jc[siz[u]-1-siz[v]]%mod)*(invjc[siz[u]-1]*jc[siz[v]]%mod))%mod)*inv(dp[v])%mod;
        dp[v]=dp[v]*invjc[siz[v]-1]%mod*jc[n-1]%mod*x%mod*invjc[siz[u]-siz[v]]%mod;
        int y=siz[u];
        siz[u]=n-siz[v];
        siz[v]=n;
        hgdfs(v,u);
        siz[u]=y;
    }
}
signed main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    jc[0]=1;
    for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
    invjc[n]=inv(jc[n]);
    for(int i=n-1;i>=0;--i) invjc[i]=invjc[i+1]*(i+1)%mod;
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    sizdfs(1,-1);
    dpdfs(1,-1);//cout<<dp[1]<<endl;
    hgdfs(1,-1);
    for(int i=1;i<=n;i++) cout<<dp[i]<<"\n";
    return 0;
}
/*
3
1 2
1 3
*/

回到本题,这题要求给定一个长度为 n n n的排列,分别给出这个排列的前缀最大值和后缀最大值位置,求合法的排列个数。然后有个很巧妙的思想,把序列想成一个树,排列个数就是树的拓扑序,树上的边就是题目给定限制,在本题中就是若 u u u连边到 v v v,代表 v a l [ u ] > v a l [ v ] val[u]>val[v] val[u]>val[v],根据题意将 a [ i + 1 ] a[i+1] a[i+1]连边到 a [ i ] a[i] a[i],将 a [ i ] a[i] a[i]连边到 [ a [ i ] + 1 , a [ i + 1 ] ) [a[i]+1,a[i+1]) [a[i]+1,a[i+1]),将 b [ i − 1 ] b[i-1] b[i1]连边 b [ i ] b[i] b[i] b [ i ] b[i] b[i]连边到 ( b [ i − 1 ] , b [ i ] − 1 ] (b[i-1],b[i]-1] (b[i1],b[i]1]。代码如下

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
const int mod=1e9+7;
const int N=4e5+10;
const db PI=acos(-1);
int jc[N+10],invjc[N+10];
int T,n,m1,m2,tot,first[N],nxt[N],to[N],a[N],b[N],siz[N],dp[N];
int qpow(int a,int b)
{
    int f=1;
    while(b)
    {
        if(b%2==1) f=f*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return f;
}
int inv(int x){return qpow(x,mod-2);}
void add(int a,int b)
{
    nxt[++tot]=first[a];
    first[a]=tot;
    to[tot]=b;
}
void init()
{
    tot=0;
    for(int i=1;i<=n;i++)
    {
        siz[i]=1;
        first[i]=dp[i]=0;
    }
}
void sizdfs(int u,int fa)
{
    for(int i=first[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        sizdfs(v,u);
        siz[u]+=siz[v];
    }
}
void dpdfs(int u,int fa)
{
    dp[u]=jc[siz[u]-1];
    for(int i=first[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        dpdfs(v,u);
        dp[u]=(dp[u]*dp[v]%mod)*invjc[siz[v]]%mod;
    }
}
signed main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    jc[0]=1;
    for(int i=1;i<=N;i++) jc[i]=jc[i-1]*i%mod;
    invjc[N]=inv(jc[N]);
    for(int i=N-1;i>=0;--i) invjc[i]=invjc[i+1]*(i+1)%mod;
    cin>>T;
    while(T--)
    {
        cin>>n>>m1>>m2;
        for(int i=1;i<=m1;i++) cin>>a[i];
        for(int i=1;i<=m2;i++) cin>>b[i];
        if(a[1]!=1||b[m2]!=n||a[m1]!=b[1])
        {
            cout<<0<<"\n";
            continue;
        }
        init();
        for(int i=1;i<m1;i++)
        {
            for(int j=a[i]+1;j<=a[i+1]-1;j++) add(a[i],j);
            add(a[i+1],a[i]);
        }
        for(int i=m2;i>1;i--)
        {
            for(int j=b[i]-1;j>b[i-1];j--) add(b[i],j);
            add(b[i-1],b[i]);
        }
        sizdfs(a[m1],-1);
        dpdfs(a[m1],-1);
        cout<<dp[a[m1]]<<"\n";
    }
}
  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值