bzoj1494: [NOI2007]生成树计数
题意
求使所有点连成一棵生成树的方案数
题解
大家博客写的都很详细啊…
下面有注释……
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define M 65521
#define ll long long
int m,tot=0,f[10],sta[60][10],a[10],cnt[55],num[10],edge[10];
ll n;
struct matrix{
int mat[60][60];
matrix(bool ff){
memset(mat,0,sizeof(mat));
if(ff) for(int i=1;i<=tot;i++) mat[i][i]=1;
}
matrix operator*(matrix b){
matrix re(0);
for(int i=1;i<=tot;i++)
for(int j=1;j<=tot;j++)
for(int k=1;k<=tot;k++)
re.mat[i][j]=(re.mat[i][j]+(ll)mat[i][k]*b.mat[k][j])%M;
return re;
}
matrix operator^(ll k){
matrix re(1),base(0);
memcpy(base.mat,mat,sizeof(mat));
while(k){
if(k&1) re=re*base;
base=base*base;k>>=1;
}return re;
}
}trans(0);
inline void calc(int id){ //初始化每种情况个数
memset(f,0,sizeof(f));cnt[id]=1;
for(int i=1;i<=m;i++) f[sta[id][i]]++;
for(int i=1;f[i];i++) if(f[i]>2) cnt[id]*=(int)pow(f[i],f[i]-2);
}
void dfs1(int x,int s){ //前x-1个被分成了s块
if(x==m+1){
tot++;
for(int i=1;i<=m;i++) sta[tot][i]=a[i];
calc(tot);return;
}
for(int i=1;i<=s+1;i++) a[x]=i,dfs1(x+1,max(i,s));
}
inline bool issame(int x[],int b[]){
for(int i=1;i<=m;i++) if(x[i]!=b[i]) return 0;
return 1;
}
inline void get1(int id){ //求此时状态id转移到的状态是什么
memcpy(a,sta[id],sizeof(a));
for(int i=1;i<=m;i++)
if(edge[i]){
if(!a[m+1]) a[m+1]=a[i];
else{
int x=a[i];
for(int j=1;j<=m;j++) if(a[j]==x) a[j]=a[m+1];
}
}
for(int i=1;i<=m;i++) a[i]=a[i+1];
memset(num,0,sizeof(num));int num1=0;
for(int i=1;i<=m;i++){ //求这种转移到的方案最小表示法
if(!num[a[i]]) num[a[i]]=++num1;
a[i]=num[a[i]];
}
for(int i=1;i<=tot;i++) if(issame(a,sta[i])){trans.mat[id][i]++;return;}
}
void dfs2(int id,int x){ //求状态id能转移到什么状态
if(x==m+1){get1(id);return;}
dfs2(id,x+1);
if(!f[sta[id][x]]){
f[sta[id][x]]=1;edge[x]=1;
dfs2(id,x+1);
f[sta[id][x]]=0;edge[x]=0;
}
}
int main(){
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%d%lld",&m,&n);
dfs1(1,0); //预处理前m个的状态
for(int i=1;i<=tot;i++){
memset(f,0,sizeof(f));
memset(edge,0,sizeof(edge));
bool flag=1;
for(int j=2;j<=m;j++) if(sta[i][j]==1){flag=0;break;}
if(flag){ //如果flag为真即之前没有出现过1,也就是没有和第一个点相连的,所以下一个必须与第一个点相连
f[1]=1;edge[1]=1;dfs2(i,2);
}else dfs2(i,1);
}trans=trans^(n-m);ll ans=0;
for(int i=1;i<=tot;i++) ans=(ans+(ll)trans.mat[i][1]*cnt[i])%M;
printf("%lld\n",ans);
return 0;
}