[NOI2007]生成树计数

题意

一个 n n 个点的树,点i只能向 [ik,i1] [ i − k , i − 1 ] 内的点连边,求有标号生成树的个数

n1015,k5 n ≤ 10 15 , k ≤ 5


题解

题面误导向系列,显然不能像他说的那样做吧

因为发现 k k 特别小,所以可以考虑用状压表示i和前 k1 k − 1 个点的联通情况

考虑 DP,f[i][j] D P , f [ i ] [ j ] 表示前 i i 个点,[ik,i]这些点的连通性是 j j 的方案数

考虑到有些状态本质上是一样的(e.g. 123=132)

所以考虑用最小表示法来存状态

k=3 k = 3 时有 5 5 种状态:111,112,121,122,123
数字表示第 i i 属于第几个集合
121表示 {1,3},{2} { 1 , 3 } , { 2 } 1,3 1 , 3 联通

通过爆搜搜出所有满足最小表示法的状态 (k=5 ( k = 5 时有 52 52 ) )

这里稍微解释一下,就是假设当前点所在的最大的集合编号是i
那么下一个点最多只能在第 i+1 i + 1 个集合,否则就是非法的,不然就更新最大值
这个爆搜完全可以直接每个点开一个数组去判断,代码里用压位二进制写的

然后一个最小表示法实际上对应很多种树 (n ( n 个点的有标号无根树个数的 nn2) n n − 2 )

e.g. 11122 e . g .   11122 表示 {1,2,3},{4,5} { 1 , 2 , 3 } , { 4 , 5 } 对应的树的种数是 332×222=3 3 3 − 2 × 2 2 − 2 = 3

暴力枚举新加入一个点之后两个状态之间的转移

也就是 2k 2 k 枚举新的点和前面的那些点 ([ik,i1]) ( [ i − k , i − 1 ] ) 是否连边

1. 1. 下一状态不能有环
2. 2. 下一状态必须与当前状态所有的点都连通
3. 3. 可以用并查集来判断并求出所有满足条件的转移数
4. 4. 转移的时候把点 ik1 i − k − 1 当做 0 0 号点,i点当做 k k 号点,会比较好转移一些

g[j][k]表示从 j j 状态转移到k状态的方案数

Cnt C n t 为状态数,那么有

f[i][j]=k=0Cntf[i1][k]×g[k][j] f [ i ] [ j ] = ∑ k = 0 C n t f [ i − 1 ] [ k ] × g [ k ] [ j ]

然后可以发现这就是矩阵乘法的一步,所以把 g g 表示成矩阵然后直接矩阵快速幂就好了

最后把矩阵快幂得到的矩阵乘上f[k]这个矩阵的和就是答案了

因为最后的状态必须是 11 1 … 1 (所有点都要联通),所以只要算上第一个状态的答案就好了

这个代码可能要认真看几遍才能看得懂

感觉还是比较短的

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int K=6,S=55,P=65521,T[]={1,1,1,3,16,125};
typedef long long ll;
int k,Cnt,ans,sum[K],fa[K],f[S],sta[S],pos[1<<16];ll n;
inline int pls(int a,int b){return a+=b,a<P?a:a-P;}
struct matrix{
    int a[S][S];
    matrix(int x=0){memset(a,0,sizeof a);if(x)fp(i,1,Cnt)a[i][i]=1;}
    inline int*operator[](int x){return a[x];}
    inline matrix operator*(matrix b){
        matrix c;
        fp(i,1,Cnt)fp(k,1,Cnt)if(a[i][k])fp(j,1,Cnt)
            c[i][j]=pls(c[i][j],(ll)a[i][k]*b[k][j]%P);
        return c;
    }
    inline matrix operator^(ll b){
        matrix x(1),a=*this;
        for(;b;b>>=1,a=a*a)if(b&1)x=x*a;
        return x;
    }
}g;
inline bool chk(int s){
    int tp=1;
    for(int i=0;i<3*k;tp|=1<<(((s>>i)&7)),i+=3)
        fp(j,0,((s>>i)&7)-1)if(!(tp&(1<<j)))return 0;
    return 1;
}
void dfs(int d,int s){
    if(d==k){if(chk(s))sta[++Cnt]=s,pos[s]=Cnt;return;}
    fp(i,1,k)dfs(d+1,s|(i<<(3*d)));
}
int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
inline void Getnx(int i,int s,int nw){
    fp(j,0,k)fa[j]=j;
    fp(j,0,k)fp(l,j+1,k)
        if(((nw>>(3*j))&7)==((nw>>(3*l))&7))fa[gf(j)]=gf(l);
    fp(j,0,k)if(s&(1<<j)){
        if(gf(j)^gf(k))fa[fa[j]]=k;
        else return;
    }
    int tp=0,nx=0,fg=0;
    fp(j,1,k)if(gf(0)==gf(j)){fg=1;break;};
    if(!fg)return;
    fp(j,0,k-1)if(!(nx&(7<<(j*3)))){
        nx|=++tp<<(j*3);
        fp(l,j+1,k-1)if(gf(j+1)==gf(l+1))
            nx|=tp<<(l*3);
    }
    ++g[i][pos[nx]];
}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    scanf("%d%lld",&k,&n);dfs(0,1);
    fp(i,1,Cnt){
        f[i]=1;int nw=sta[i];
        memset(sum,0,sizeof sum);
        fp(j,0,k-1)++sum[(nw>>(j*3))&7];
        fp(j,1,k)f[i]*=T[sum[j]];
        fp(s,0,(1<<k)-1)Getnx(i,s,nw);
    }g=g^(n-k);
    fp(i,1,Cnt)ans=pls(ans,f[i]*g[i][1]%P);
    printf("%d",ans);
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值