题解 ARC106F【Figures】/ SS230912A【圣诞树】

problem

提示:本题输入量较大,请采用较快的读入方式

圣诞节到了,小P很孤独。

他只有一棵光秃秃的圣诞树,所以他决定玩玩它。不幸的是,这棵树被玩坏了,所以小P想将它复原。

小P的圣诞树是一棵 \(n\) 个节点的树,每个点是一个小球,第 \(i\) 个球上面有 \(d_i\) 个孔,孔之间是有区别的。

小P需要用 \(n-1\) 条绳子将这些点连成一棵树。每个绳子应该连接两个不同的点的两个孔,且每个孔里最多有 \(1\) 根绳子。小P想知道,他能够复原出多少种不同的树。由于这个数太大了,他只想知道答案模 \(m\) 的结果。注意 \(m\) 不一定为质数。

定义两棵树相同当且仅当对于每一条边,它插入的两个孔在两棵树中相同,详见样例解释。

\(n\leq 10^5,d_i<m\leq 10^9\)

solution

考虑 prufer 序列,设 \(c_i\) 表示点 \(i\) 在 prufer 序列中出现次数,考虑限制:\(0\leq c_i\leq d_i-1,\sum c_i=n-2\)。确定这个以后,可以算出穿孔的方案数为:

\[\begin{aligned} \frac{(n-2)!}{\prod c_i!}\prod_iA_{d_i}^{c_i+1}=\frac{(n-2)!\prod d_i!}{\prod c_i!(d_i-c_i-1)!}\\ =\frac{(n-2)!\prod d_i\cdot(d_i-1)!}{\prod c_i!(d_i-c_i-1)!}=(n-2)!\prod d_i\prod\binom{d_i-1}{c_i} \end{aligned}\]

前面那一坨是常数,扔掉;后面那一坨如果要枚举 \(\sum c_i=n-2\),则可以范德蒙德卷积过去,变成 \(\binom{\sum (d_i-1)}{\sum c_i}=\binom{\sum d_i-n}{n-2}\),然后和刚才的常数乘起来,\((n-2)!\) 刚好消掉,最后就是两个循环的事情。

code

点击查看代码
int main(){
//  #ifdef LOCAL
//      freopen("input.in","r",stdin);
//  #endif
    freopen("tree.in","r",stdin),freopen("tree.out","w",stdout);
    scanf("%d%u",&n,&P);
    mint ans=1;
    for(int i=1,x;i<=n;i++) scanf("%d",&x),ans*=x,dsum+=x;
    for(int i=1;i<=n-2;i++) ans*=dsum-n-i+1;
    printf("%d\n",raw(ans));
    return 0;
}

考场上没有发现 \((n-2)!\) 能消,写了暴力求组合数:

//O(mlnm)
#include <cassert>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
unsigned P;
struct modint{
    unsigned v; modint():v(0){}
    template<class T> modint(T x):v((x%int(P)+int(P))%int(P)){}
    modint operator-()const{return modint(P-v);}
    modint&operator+=(const modint&rhs){if(v+=rhs.v,v>=P) v-=P; return *this;}
    modint&operator-=(const modint&rhs){return *this+=-rhs;}
    modint&operator*=(const modint&rhs){v=1ull*v*rhs.v%P; return *this;}
    friend int raw(const modint&self){return self.v;}
    friend modint qpow(modint a,LL b){modint r=1;for(;b;b>>=1,a*=a) if(b&1) r*=a; return r;}
    friend modint operator+(modint lhs,const modint&rhs){return lhs+=rhs;}
    friend modint operator-(modint lhs,const modint&rhs){return lhs-=rhs;}
    friend modint operator*(modint lhs,const modint&rhs){return lhs*=rhs;}
    friend bool operator==(const modint&lhs,const modint&rhs){return lhs.v==rhs.v;}
    friend bool operator!=(const modint&lhs,const modint&rhs){return lhs.v!=rhs.v;}
};
typedef modint mint;
int n;
LL dsum=0,a[1<<20];
int cnt[1<<20];
bool isp[1<<20];
int divide(LL &x,int p){
    int res=0;
    while(x%p==0) x/=p,res++;
    return res;
}
void sieve(int m){
    memset(isp,1,sizeof isp),isp[1]=0;
    for(int i=2;i<=m;i++) if(isp[i]){
        cnt[i]--;
        for(int j=i+i;j<=m;j+=i){
            LL k=j;
            cnt[i]-=divide(k,i),isp[j]=0;
        }
    }
}
mint binom(LL n,int m){
//n<=1e12,m<=1e6
    debug("binom(%lld,%d)\n",n,m);
//  mint up=1,dw=1;
//  for(int i=n;i>n-m;i--) up*=i;
//  for(int i=1;i<=m;i++) dw*=i;
//  return up*qpow(dw,P-2);
    //[n-m+1,n]
    for(int i=1;i<=m;i++) a[i]=n-m+i;//,debug("a[%d]=%lld\n",i,a[i]);
    sieve(m);
    //for(int i=1;i<=m;i++) debug("cnt[%d]=%d\n",i,cnt[i]);
    for(int i=1;i<=m;i++) if(isp[i]){
        int st=LL((n-m)/i+1)*i-(n-m);
        //assert(a[st]%i==0);
        for(int j=st;j<=m;j+=i) /*assert(a[j]%i==0),*/cnt[i]+=divide(a[j],i);
    }
    mint ans=1;
    for(int i=1;i<=m;i++) ans*=a[i]*qpow(mint(i),cnt[i]);//,debug("a[%d]=%lld,cnt[%d]=%d\n",i,a[i],i,cnt[i]);
    return ans;
}
posted @ 2023-09-13 18:46  caijianhong  阅读(3)  评论(0编辑  收藏  举报