zoukankan      html  css  js  c++  java
  • [BZOJ5305] [HAOI2018] 苹果树 数学 组合计数

    Summary

    题意很清楚:

    (C) 在自己家的花园里种了一棵苹果树, 树上每个结点都有恰好两个分支.

    经过细心的观察, 小 (C) 发现每一天这棵树都会生长出一个新的结点.

    第一天的时候, 果树会长出一个根结点, 以后每一天, 果树会随机选择一个当前树中没有长出过结点的分支, 然后在这个分支上长出一个新结点, 新结点与分支所属的结点之间连接上一条边.

    (C) 定义一棵果树的不便度为树上两两结点之间的距离之和, 两个结点之间 的距离定义为从一个点走到另一个点的路径经过的边数.

    现在他非常好奇, 如果 (N) 天之后小 (G) 来他家摘苹果, 这个不便度的期望 (E) 是多少. 但是小 (C) 讨厌分数, 所以他只想知道 (E imes N!)(P) 取模的结果, 可以证明这是一个整数.

    Solution

    一开始想用树形DP,但是发现子树间不是独立的,所以用不了。

    首先可以发现,每次加上一个点以后,其父节点少了一个空位,但是他自己有提供了两个空位。

    因此每增加一个节点,都会多一个空位。添加第(i)个点的时候,就有(i)个空位。

    所以总方案数为(N!)。因此,(E imes N!)就是所有形态的树的的不便度总和。

    根据这种题目的套路,应该考虑每条边的贡献。但是发现所有的边都是不确定的。

    但是好好想想,其实只要一个点的子树大小确定了,其连向父节点的边的贡献也就定下来了。每一个子树内的点和子树外的点的搭配,都会经过这条边。因此,假设子树大小为(j),那么那个点连向其父节点的边的贡献就是(jcdot(n-j))

    然后考虑每一个子树的根节点,设其为(i)。因为加点是有顺序的,我们不妨假设编号为(i)的点是第(i)个加入的。

    那么这(i)个点的形态就有(i!)种。然后子树内,也有(j!)种形态。

    但是子树内有哪些点呢?

    首先这些点的组合肯定是(C_{n-i}^{j-1})种。因为加点顺序是定下来的,所以不需要考虑加点顺序带来的不同的树。

    然后只剩下了子树外的。

    子树外呢,前(i-1)个点加完以后,有(i)个空位。有一个空位被(i)占掉了,那么还剩下(i-1)个空位。

    因为除了(i)这棵子树,以及已经加过的点,还剩下(n-i-j+1)个点。

    每次加一个点,多一个空位。所以每一多一个空位,直到加完,那么最后一次加点前的空位数为(i-1+n-i-j+1-1=n-j-1),注意最后一次(-1)是因为最后一次还没有加点。

    所以那些点的形态有(prod limits_{k=i-1}^{n-j-1} k)个。

    那么总共,第(i)个点的子树大小为(j)的贡献就是

    [jcdot(n-j)cdot j! i!cdot C_{n-i}^{j-1}cdot prod_{k=i-1}^{n-j-1}k ]

    这个东西再化一化,后面的连乘和前面的(i!, (n-j))可以合并到一起,然候再拎出中间重复的。

    [ij(i-1)cdot j!cdot (n-j)!cdot C_{n-i}^{j-1} ]

    然候就可以(O(n^2)) 枚举(i, j),计算出来了。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #define REP(i,a,n) for(register int i=a;i<=n;++i)
    #define dbg(...) fprintf(stderr,__VA_ARGS__)
    typedef long long ll;
    const int N=2000+7;
    int n,P,C[N][N],fac[N],ans;
    inline void SADD(int&x,const int&y){x+=y;x>=P?x-=P:0;}//´íÎó±Ê¼Ç£º-=µÈºÅÍü´òÁË 
    inline int SMOD(int x){return x>=P?x-P:x;}
    inline void Preprocess(){
    	C[0][0]=fac[0]=1;
    	REP(i,1,n){
    		C[i][0]=1;fac[i]=(ll)fac[i-1]*i%P;
    		REP(j,1,i)C[i][j]=SMOD(C[i-1][j-1]+C[i-1][j]);
    	}
    }
    int main(){
    	scanf("%d%d",&n,&P);
    	Preprocess();
    	REP(i,2,n)REP(j,1,n-i+1)SADD(ans,(ll)j*i*(i-1)%P*fac[j]%P*fac[n-j]%P*C[n-i][j-1]%P);
    	printf("%d
    ",ans);
    }
    
  • 相关阅读:
    mock 数据模拟
    利用css绘制三角形,半圆等形状
    页面底部固定
    Form Data格式传参
    element 页面显示效果及需要注意的点
    vue 组件加载顺序
    vue-router 导航钩子
    vue 总结
    前端开发的碎碎念
    值匹配的方式
  • 原文地址:https://www.cnblogs.com/hankeke/p/BZOJ305.html
Copyright © 2011-2022 走看看