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

problem

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

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

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

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

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

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

n105,di<m109

solution

考虑 prufer 序列,设 ci 表示点 i 在 prufer 序列中出现次数,考虑限制:0cidi1,ci=n2。确定这个以后,可以算出穿孔的方案数为:

(n2)!ci!iAdici+1=(n2)!di!ci!(dici1)!=(n2)!di(di1)!ci!(dici1)!=(n2)!di(di1ci)

前面那一坨是常数,扔掉;后面那一坨如果要枚举 ci=n2,则可以范德蒙德卷积过去,变成 ((di1)ci)=(dinn2),然后和刚才的常数乘起来,(n2)! 刚好消掉,最后就是两个循环的事情。

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;
}

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

//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 @   caijianhong  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示