bzoj1494: [NOI2007]生成树计数

传送门
将k个点的连通性用最小表示法压成状态,那么最多有52种状态
最小表示法中,f[i]表示最小的与其联通的点编号。
计算出每个状态的生成树个数,作为初始行向量A
对于每种状态考虑新加入一个点并向这k个点连边,每种连法可以转移到哪些状态,得到转移矩阵B
那么答案就是A∗Bn[所有点都连通的状态]
就是代码炒鸡长。

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mo 65521
#define uint unsigned int
#define ll long long
using namespace std;
int k,tot,pos[33333];
ll n;
uint A[60],ans;
struct mat{
    uint a[60][60];
    mat(int fl){
        memset(a,0,sizeof(a));
        for (int i=1;i<=tot;i++)
            a[i][i]=fl;
    }
    uint* operator [](int x){
        return a[x];
    }
    friend mat& operator *=(mat &x,mat y){
        mat z(0);
        for (int i=1;i<=tot;i++)
            for (int j=1;j<=tot;j++)
                for (int k=1;k<=tot;k++)
                    z[i][j]=(z[i][j]+x[i][k]*y[k][j])%mo;
        return x=z; 
    }
}a(0);
mat power(mat x,ll y){
    mat s(1);
    for (;y;y/=2,x*=x)
        if (y&1) s*=x;
    return s;
}
void express(int a[],int x){
    for (int i=0;i<k;i++)
        a[i]=(x>>i*3)&7;
}
int impress(int a[]){
    int s=0;
    for (int i=0;i<k;i++)
        s|=a[i]<<i*3;
    return s;
}
void stdsize(int a[]){
    static int b[10];
    int cnt=0;
    memset(b,0,sizeof(b));
    for (int i=0;i<k;i++)
        if (!b[a[i]]) b[a[i]]=++cnt;
    for (int i=0;i<k;i++) a[i]=b[a[i]];
}
bool ok(int x){
    static int a[10];
    int now=1;
    express(a,x);
    for (int i=0;i<k;i++)
        if (!a[i]||a[i]>now) return 0;
        else if (a[i]==now) now++;
    return 1;
}
void calc1(int x){
    static int A[10],b[10],cnt[10];
    int lim=0;
    express(A,x);
    memset(cnt,0,sizeof(cnt));
    for (int i=0;i<k;i++){
        cnt[A[i]]++;
        lim=max(lim,A[i]);
    }
    for (int _x=0;_x<1<<lim;_x++){
        if (cnt[1]==1&&_x%2==0) continue;
        int tmp=1;
        memcpy(b,A,sizeof(A));
        b[k]=9;
        for (int i=1;i<=lim;i++)
            if (_x&(1<<i-1)) tmp*=cnt[i];
        for (int i=0;i<k;i++)
            if (_x&(1<<A[i]-1)) b[i]=9;
        stdsize(b+1);
        a[pos[x]][pos[impress(b+1)]]=tmp;
    }
}
int power(int x,int y){
    int s=1;
    for (;y>0;y--) s*=x;
    return s; 
}
void calc2(uint A[],int x){
    static int a[10],cnt[10];
    int s=1;
    express(a,x);
    memset(cnt,0,sizeof(cnt));
    for (int i=0;i<k;i++) cnt[a[i]]++;
    for (int i=1;cnt[i];i++)
        s*=power(cnt[i],cnt[i]-2);
    A[pos[x]]=s;
}
int main(){
    scanf("%d%lld",&k,&n);
    if (k>n) k=n;
    for (int i=0;i<1<<k*3;i++)
        if (ok(i)) pos[i]=++tot;
    for (int i=0;i<1<<k*3;i++)
        if (pos[i]){
            calc1(i);
            calc2(A,i);
        }
    mat B=power(a,n-k);
    for (int i=1;i<=tot;i++)
        ans=(ans+A[i]*B[i][1])%mo;
    printf("%u",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值