【XSY1295】calc n个点n条边无向连通图计数 prufer序列

题目大意

  求\(n\)个点\(n\)条边的无向连通图的个数

  \(n\leq 5000\)

题解

  显然是一个环上有很多外向树。

  首先有一个东西:\(n\)个点选\(k\)个点作为树的根的生成森林个数为:
\[ \binom{n}{k}\times n^{n-k-1}\times k \]
  前面\(\binom{n}{k}\)是这些根的选编号的方案数,后面是prufer序列得到的:前面\(n-k-1\)个数可以是\(1\)~\(n\),第\(n-k\)个数是\(1\)~\(k\)

  我的理解是:每个序列决定了一部分点作为"叶子节点",剩下的每个点按顺序选一个编号最小的"叶子节点"作为这个点的儿子(选编号最小的是因为1.如果一个点可以选多个儿子就不会重复计数;2.两个数的先后顺序不同,那么选的儿子也不同,会让先后顺序成为影响因素),然后如果这个点不能再选儿子,那么这个点就会成为"叶子节点"。选了\(n-k-1\)个点后会剩下\(k\)个根和一个不是根的点,然后\(k\)个根中的一个点连向剩下这个点。

  最后\(k\)个点的环的排列方式有\(\frac{(k-1)!}{2}\)。你可以选编号最小的点为"根",剩下\(k-1\)个点每次选一个点连向上一个点,最后一个点再连向第一个点。因为环可以翻转,所以方案数要除以\(2\)。你也可以认为是先生成一个排列,然后旋转这个环(除以\(k\)),然后翻转这个环(除以\(2\))。

  最终的式子是
\[ \begin{align} &~~~~~\sum_{k=3}^{n}\binom{n}{k}\times n^{n-k-1}\times k\times \frac{(k-1)!}{2}\\ &=\sum_{k=3}^{n}\frac{n!\times n^{n-k-1}\times k\times (k-1)!}{k!\times (n-k)!\times 2}\\ &=\sum_{k=3}^{n}\frac{n!n^{n-k-1}}{2(n-k)!} \end{align} \]
  写个高精度什么的乱搞一下就可以了。

  时间复杂度:\(O(n^2)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int p=10000;
struct bign
{
    int a[5010];
    bign()
    {
        memset(a,0,sizeof a);
    }
    int &operator [](int x)
    {
        return a[x];
    }
    bign &operator *=(int v)
    {
        int i,s,g=0;
        bign &a=*this;
        for(i=1;i<=5000;i++)
        {
            s=g+a[i]*v;
            g=s/p;
            a[i]=s%p;
        }
        return a;
    }
    bign &operator /=(int v)
    {
        int i,s,g=0;
        bign &a=*this;
        for(i=5000;i>=1;i--)
        {
            s=g*p+a[i];
            a[i]=s/v;
            g=s%v;
        }
        g=0;
        for(i=1;i<=5000;i++)
        {
            s=g+a[i];
            a[i]=s%p;
            g=s/p;
        }
        return a;
    }
    bign &operator +=(bign &b)
    {
        int i,s,g=0;
        bign &a=*this;
        for(i=1;i<=5000;i++)
        {
            s=a[i]+b[i]+g;
            a[i]=s%p;
            g=s/p;
        }
        return a;
    }
};
bign a,ans;
int main()
{
    int n;
    scanf("%d",&n);
    int i;
    a[1]=1;
    for(i=2;i<=n-1;i++)
        a*=i;
    ans+=a;
    for(i=n-1;i>=3;i--)
    {
        a*=n;
        a/=n-i;
        ans+=a;
    }
    ans/=2;
    for(i=5000;!ans[i];i--);
    printf("%d",ans[i]);
    for(i--;i;i--)
        printf("%04d",ans[i]);
    printf("\n");
    return 0;
}

转载于:https://www.cnblogs.com/ywwyww/p/8511316.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值