zoukankan      html  css  js  c++  java
  • bzoj1494: [NOI2007]生成树计数

    Description

    最近,小栋在无向连通图的生成树个数计算方面有了惊人的进展,他发现:
    ·n个结点的环的生成树个数为n。
    ·n个结点的完全图的生成树个数为n^(n-2)。这两个发现让小栋欣喜若狂,由此更加坚定了他继续计算生成树个数的
    想法,他要计算出各种各样图的生成树数目。一天,小栋和同学聚会,大家围坐在一张大圆桌周围。小栋看了看,
    马上想到了生成树问题。如果把每个同学看成一个结点,邻座(结点间距离为1)的同学间连一条边,就变成了一
    个环。可是,小栋对环的计数已经十分娴熟且不再感兴趣。于是,小栋又把图变了一下:不仅把邻座的同学之间连
    一条边,还把相隔一个座位(结点间距离为2)的同学之间也连一条边,将结点间有边直接相连的这两种情况统称
    为有边相连,如图1所示。
    小栋以前没有计算过这类图的生成树个数,但是,他想起了老师讲过的计算任意图的生成树个数的一种通用方法:
    构造一个n×n的矩阵A={aij},其中
    其中di表示结点i的度数。与图1相应的A矩阵如下所示。为了计算图1所对应的生成数的个数,只要去掉矩阵A的最
    后一行和最后一列,得到一个(n-1)×(n-1)的矩阵B,计算出矩阵B的行列式的值便可得到图1的生成树的个数所以
    生成树的个数为|B|=3528。小栋发现利用通用方法,因计算过于复杂而很难算出来,而且用其他方法也难以找到更
    简便的公式进行计算。于是,他将图做了简化,从一个地方将圆桌断开,这样所有的同学形成了一条链,连接距离
    为1和距离为2的点。例如八个点的情形如下:
    这样生成树的总数就减少了很多。小栋不停的思考,一直到聚会结束,终于找到了一种快捷的方法计算出这个图的
    生成树个数。可是,如果把距离为3的点也连起来,小栋就不知道如何快捷计算了。现在,请你帮助小栋计算这类
    图的生成树的数目。

    Input

    包含两个整数k,n,由一个空格分隔。k表示要将所有距离不超过k(含k)的结点连接起来,n表示有n个结点。

    Output

    输出一个整数,表示生成树的个数。由于答案可能比较大,所以你 只要输出答案除65521 的余数即可。

    从左到右处理,记录最后k个点间的连通性可以进行递推,连通情况最多有Bell(5)=52种,因此可以用矩阵乘法优化递推

    #include<bits/stdc++.h>
    typedef unsigned int u32;
    typedef u32 mat[57][57];
    const u32 P=65521;
    mat c,x,y;
    int N,k;
    long long n;
    inline u32 fix(u32 x){
        return x-(x>>16)*P;
    }
    void clr(mat a,u32 v){
        for(int i=1;i<=N;a[i][i]=v,++i)
        for(int j=1;j<=N;++j)
            a[i][j]=0;
    }
    void mul(mat a,mat b){
        clr(c,0);
        for(int i=1;i<=N;++i)
        for(int k=1;k<=N;++k)if(a[i][k])
        for(int j=1;j<=N;++j)
            c[i][j]+=fix(a[i][k]*b[k][j]);
        for(int i=1;i<=N;++i)
        for(int j=1;j<=N;++j)
            a[i][j]=c[i][j]%P;
    }
    int ids[33333],idp=0;
    int tr[57][32],st[57],f[11],g[7][57],ed[11];
    void dfs(int w,int m,int v){
        if(w==k){
            ids[v]=++idp;
            st[idp]=v;
            return;
        }
        for(int i=0;i<=m;++i)dfs(w+1,m+(i==m),v|i<<3*w);
    }
    void upd(int k){
        for(int i=0;i<k;++i)ed[i]=-1;
        for(int i=0,p=0;i<k;++i){
            if(ed[f[i]]<0)ed[f[i]]=p++;
            f[i]=ed[f[i]];
        }
    }
    void mg(int a,int b,int k){
        int z=f[b];
        for(int i=0;i<k;++i)if(f[i]==z)f[i]=f[a];
    }
    int main(){
        scanf("%d%lld",&k,&n);
        dfs(0,0,0);
        N=idp;
        for(int i=1;i<=idp;++i){
            for(int j=0,ab;j<(1<<k);++j){
                for(int a=0;a<k;++a)f[a]=st[i]>>a*3&7;
                ab=1;
                for(int a=0,pv=-1;a<k;++a)if(j>>a&1){
                    if(pv<0)pv=a;
                    else if(f[pv]==f[a])ab=0;
                    else mg(pv,a,k);
                }
                if(ab){
                    upd(k);
                    int v=0;
                    for(int a=0;a<k;++a)v|=f[a]<<a*3;
                    tr[i][j]=ids[v];
                }
                
                for(int a=0;a<k;++a)f[a]=st[i]>>a*3&7;
                f[k]=k;
                ab=1;
                for(int a=0,pv=-1;a<k;++a)if(j>>a&1){
                    if(f[k]==f[a])ab=0;
                    else mg(k,a,k+1);
                }
                if(ab){
                    ab=0;
                    for(int i=1;i<=k;++i)ab|=f[0]==f[i];
                    if(ab){
                        for(int i=0;i<k;++i)f[i]=f[i+1];
                        upd(k+1);
                        int v=0;
                        for(int a=0;a<k;++a)v|=f[a]<<a*3;
                        ++y[ids[v]][i];
                    }
                }
            }
        }
        g[1][idp]=1;
        for(int t=1;t<k;++t){
            int L=1<<t,R=L<<1;
            for(int i=1;i<=idp;++i)if(g[t][i]){
                for(int j=L;j<R;++j){
                    (g[t+1][tr[i][j]]+=g[t][i])%=P;
                }
            }
        }
        clr(x,1);
        for(n-=k;n;n>>=1,mul(y,y))if(n&1)mul(x,y);
        int ans=0;
        for(int i=1;i<=idp;++i)ans+=fix(x[1][i]*g[k][i]);
        printf("%d
    ",ans%P);
        return 0;
    }
  • 相关阅读:
    1. while循环(当循环) 2. do{}while()循环 3. switch cose(多选一) 例子:当选循环下求百鸡百钱 用 switch cose人机剪刀石头布
    JS。 问题类型:穷举,迭代。两个关键词:break和continue
    for循环计算游戏通关分数
    36抽8 模拟抽奖
    冒泡排序
    折纸---珠穆朗玛问题----简单for 循环
    水仙花数------"水仙花数 "是指一个三位数,其各位数字立方和等于该数本身。(for循环的嵌套)
    九九乘法表---for循环的嵌套
    百鸡百钱===百马百担====for循环嵌套
    控制台输入年龄,根据年龄输出不同的提示 ------if……else if ……else 语句
  • 原文地址:https://www.cnblogs.com/ccz181078/p/7122521.html
Copyright © 2011-2022 走看看