bzoj 1494 生成树计数

坑了好多天的题,终于补上了
首先发现 \(i\) 这个点和 \(i-k\) 之前的点没有边,所以 \(i-k\) 之前的点肯定联通,只要处理中间 \(k\) 个点的联通状态就好了。我们用最小表示法,\(f[i]\) 表示最小的与 \(i\) 联通的点编号,发现状态最多有52种,然后枚举下一个点与那些点之间连边,得到转移方程,矩阵快速幂优化转移即可

(反正怎么说估计都听不懂,还是贴代码比较靠谱)

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int mod=65521;

ll n,k;
int tot,f[20],sta[100][20],a[20],cnt[100],num[20],edge[20];

struct Matrix{
	int mat[60][60];
	Matrix(int x){
		memset(mat,0,sizeof(mat));
		if(x) rep(i,1,tot) mat[i][i]=1;
	}
	Matrix operator*(Matrix ano){
		Matrix ret(0);
		rep(i,1,tot)
			rep(j,1,tot)
				rep(k,1,tot)
					ret.mat[i][j]=(ret.mat[i][j]+mat[i][k]*1ll*ano.mat[k][j])%mod;
		return ret;
	}
	Matrix operator^(ll k){
		Matrix ret(1),xx(0);
		memcpy(xx.mat,mat,sizeof(mat));
		while(k){
			if(k&1) ret=ret*xx;
			xx=xx*xx;k>>=1;
		}
		return ret;
	}
	void pr(){
		rep(i,1,tot){
			rep(j,1,tot){
				printf("%d ",mat[i][j]);
			}
			puts("");
		}
		puts("");
	}
} trans(0);

int ksm(int x,int p){
	int ret=1;
	while(p){
		if(p&1) ret=ret*x;
		x=x*x;
		p>>=1;
	}
	return ret;
}
//处理当前状态的生成树个数
//公式 大小为n的完全图生成树个数为n^(n-2)
void calc(int x){
	memset(f,0,sizeof(f));cnt[x]=1;
	rep(i,1,k) f[sta[x][i]]++;
	for(int i=1;f[i];i++) if(f[i]>2) cnt[x]*=ksm(f[i],f[i]-2);
}
//前x个分成了s个联通块的方案
void dfs1(int x,int s){
	if(x==k+1){
		tot++;
		rep(i,1,k) 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[]){
	rep(i,1,k) if(x[i]!=b[i]) return 0;
	return 1;
}
//求此时的状态是什么
inline void get1(int id){
	memcpy(a,sta[id],sizeof(a));
	rep(i,1,k){
		if(edge[i]){
			if(!a[k+1]) a[k+1]=a[i];
			else{
				int x=a[i];
				rep(j,1,k) if(a[j]==x) a[j]=a[k+1];
			}
		}
	}
	rep(i,1,k) a[i]=a[i+1];
	memset(num,0,sizeof(num));int num1=0;
	rep(i,1,k){
		if(!num[a[i]]) num[a[i]]=++num1;
		a[i]=num[a[i]];
	}
	rep(i,1,tot) if(issame(a,sta[i])){
		trans.mat[id][i]++;
		return;
	}
}
//求id能够转移到什么状态
void dfs2(int id,int x){
	if(x==k+1){
		get1(id);return;
	}
	dfs2(id,x+1);	//不连k+1到x的边
	if(!f[sta[id][x]]){	//k+1到这个联通块目前没有边,可以连
		//连k+1到x的边
		f[sta[id][x]]=1;edge[x]=1;
		dfs2(id,x+1);
		f[sta[id][x]]=0;edge[x]=0;
	}
}

int main(){
	k=read(),n=read();
	dfs1(1,0);
	//处理转移
	rep(i,1,tot){
		memset(f,0,sizeof(f));
		memset(edge,0,sizeof(edge));
		bool flag=1;
		rep(j,2,k) if(sta[i][j]==1){
			flag=0;break;
		}
		if(flag){
			//没有出现过与1相连的边,所以下一条边必须和1相连
			f[1]=1;edge[1]=1;dfs2(i,2);
		}
		else dfs2(i,1);
	}
	trans=trans^(n-k);
	ll ans=0;
	rep(i,1,tot) ans=(ans+trans.mat[i][1]*1ll*cnt[i])%mod;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2018-10-08 16:07  wawawa8  阅读(198)  评论(0编辑  收藏  举报