题解 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;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-SS230912A.html