记录编号 85134 评测结果 AAAAAAAAAA
题目名称 [NOI 2007]生成树计数 最终得分 100
用户昵称 Gravatarcstdio 是否通过 通过
代码语言 C++ 运行时间 0.184 s
提交时间 2013-12-27 16:05:47 内存使用 0.37 MiB
显示代码纯文本
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<iomanip>
using namespace std;
typedef long long ll;
const ll MOD=65521;
const int SIZEN=60;
ll N;
int K;
int SNUM;
vector<int> state;
int pow2[21]={1};
void printarray(int *s,int n){//调试接口,输出数组s下标为1~n的元素
	for(int i=1;i<=n;i++) cout<<s[i]<<" ";cout<<endl;
}
class MATRIX{
public:
	int n,m;//n行m列
	ll s[SIZEN][SIZEN];
	MATRIX(){//初始化为零
		m=n=0;
		memset(s,0,sizeof(s));
	}
	void print(void){//调试接口,输出矩阵
		cout<<"size:"<<n<<" "<<m<<endl;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++) cout<<s[i][j]<<" ";
			cout<<endl;
		}
	}
	void assign_one(int sn){//赋值为sn行的单位矩阵
		n=m=sn;
		for(int i=1;i<=n;i++) s[i][i]=1;
	}
};
MATRIX operator * (MATRIX a,MATRIX b){//矩阵乘法,结果对MOD取模
	MATRIX c;
	c.n=a.n;c.m=b.m;
	int i,j,k;
	for(i=1;i<=c.n;i++){
		for(j=1;j<=c.m;j++){
			c.s[i][j]=0;
			for(k=1;k<=a.m;k++){
				c.s[i][j]+=(a.s[i][k]*b.s[k][j])%MOD;
			}
			c.s[i][j]%=MOD;
		}
	}
	return c;
}
MATRIX quickpow(MATRIX a,ll n,int sn){//a^n,sn行/列
	MATRIX ans;ans.assign_one(sn);
	while(n){
		if(n&1) ans=ans*a;
		a=a*a;
		n>>=1;
	}
	return ans;
}
MATRIX M,D;//M是前k项的状态,D是代表转移方法的矩阵
//用八进制储存最小表示法,第i个元素的表示用代表8^i的数位表示
int getdig(int x,int k){//返回最小表示法状态x中代表8^k的数位
	return (x>>(3*k))&7;
}
int changedig(int x,int k,int t){//将x代表8^k的八进制位改为t,返回x
	x&=~(7<<(3*k));
	x|=t<<(3*k);
	return x;
}
void printdig(int x){//调试接口,输出x的各个八进制位
	for(int i=1;i<=K;i++) cout<<getdig(x,i);
}
void DFS(int x,int t,int s){//搜索,得出K个点所有可能的连通情况
	if(x==K){
		state.push_back(s);
		return;
	}
	for(int i=1;i<=t;i++) DFS(x+1,t,changedig(s,x+1,i));
	DFS(x+1,t+1,changedig(s,x+1,t+1));
}
int ufs[SIZEN]={0};//一个用来储存并查集的数组(虽然函数里都是传地址= =)
int grand(int *ufs,int x){//并查集ufs中x的根节点
	return !ufs[x]?x:(ufs[x]=grand(ufs,ufs[x]));
}
void demdl(int *ufs,int K,int s){//解调,将K个节点的状态s表示在ufs中
	int temp[SIZEN]={0};
	int i,nc;
	for(i=1;i<=K;i++){
		nc=getdig(s,i);
		if(temp[nc]==0) temp[nc]=i,ufs[i]=0;
		else ufs[i]=temp[nc];
	}
}
int mdl(int *ufs,int K){//调制,将ufs中前K个节点的状态表示为最小表示法
	int temp[SIZEN]={0};
	int i,g,tot=0;
	for(i=1;i<=K;i++){
		g=grand(ufs,i);
		if(temp[g]==0) temp[i]=temp[g]=++tot;
		else temp[i]=temp[g];
	}
	int ans=0;
	for(i=1;i<=K;i++) ans=changedig(ans,i,temp[i]);
	return ans;
}
bool check(int *ufs,int K,int cs){//对ufs中已储存的状态(K个点),检查增加一个与前K个点的连边状态用二进制表示为cs的节点后,图中是否有环(没有则返回true)
	//这会改变ufs的值
	int i,g;
	for(i=1;i<=K;i++){
		if(cs&pow2[i-1]){
			g=grand(ufs,i);
			if(g==grand(ufs,K+1)) return false;
			ufs[grand(ufs,K+1)]=g;
		}
	}
	return true;
}
void search(int x,int s){//对第x个节点已经确定,状态为s,继续搜索,这将得出前K个点所有连通情况所对应图的个数
	if(x==K){
		int temp=lower_bound(state.begin(),state.end(),s)-state.begin()+1;
		M.s[1][temp]++;
		return;
	}
	int tp[SIZEN]={0};
	int i,maxs=pow2[x]-1;//0到maxs,每个数的二进制编码都对应一个节点x+1连的点集
	demdl(tp,x,s);//解调到tp中
	for(i=0;i<=maxs;i++){
		memcpy(ufs,tp,sizeof(tp));
		if(check(ufs,x,i)){
			search(x+1,mdl(ufs,x+1));
		}
	}
}
void makestep(int *ufs){//输入时ufs中储存了K+1位的连通状态,函数将后K位的连通状态放在前K个位置以方便调制
	//将第1个点的信息放在下标为K+1的位置并相应修改ufs值,这保持了拓扑结构不变
	int i,tp=ufs[1];
	for(i=2;i<=K+1;i++){
		if(ufs[i]==1){
			ufs[i-1]=K+1;
		}
		else if(ufs[i]) ufs[i-1]=ufs[i]-1;
		else ufs[i-1]=0;
	}
	if(tp) ufs[K+1]=tp-1;
	else ufs[K+1]=0;
}
void prepare_D(void){//得到转移矩阵D
	int i,j,k,maxs=pow2[K]-1;
	int tp[SIZEN]={0};
	int temp;
	D.m=D.n=SNUM;
	for(i=1;i<=SNUM;i++){
		temp=state[i-1];
		demdl(tp,K,temp);
		for(j=0;j<=maxs;j++){
			memcpy(ufs,tp,sizeof(tp));
			if(check(ufs,K,j)){
				bool flag=false;
				for(k=2;k<=K+1;k++) if(grand(ufs,k)==grand(ufs,1)) flag=true;//必须让第一个节点能连到后面,否则就是不合法的
				if(flag){
					makestep(ufs);
					temp=lower_bound(state.begin(),state.end(),mdl(ufs,K))-state.begin()+1;
					D.s[i][temp]++;
				}
			}
		}
	}
}
void init(void){
	scanf("%d%lld",&K,&N);
	for(int i=1;i<=10;i++) pow2[i]=2*pow2[i-1];
	DFS(0,0,0);
	sort(state.begin(),state.end());//将所有可能的最小表示法排序,以方便查找
	SNUM=state.size();//最小表示法的个数
	M.n=1,M.m=SNUM;
	search(0,0);//这得到了前K项的状态矩阵
	prepare_D();//这得到了转移矩阵
}
int main(){
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	init();
	M=M*quickpow(D,N-K,SNUM);//用矩阵乘法加速转移
	printf("%lld\n",M.s[1][1]);//因为"所有点均连通"在排序后一定是首个状态
	return 0;
}