题目链接:POJ1737 Connected Graph
题目大意:
求有 (n) 个不同节点的无向连通图的个数
(nleq 50)
思路:
设 (F[i]) 为大小为 (i) 时的答案,考虑将 (i) 号点向外连的边删去,此时图分成两个连通块,对于节点 (1) ,设其所在的连通块大小为 (j) ,选取该连通块剩下节点的方案数为 (C_{i-2}^{j-1}) ,再将 (j) 和另外一个连通块的边重新连上,这两个图的方案数显然各自为 (F[j]) 和 (F[i-j]) ,枚举 (j) 到 (i) 所在的连通块原先有那些边相连,由于图是连通的,所以不能一条边不连,这里的方案数为 (2^j-1) 。
这样,我们得到了递推式:
[F[i]=sum_{j=1}^{i-1}{C_{i-2}^{j-1}*F[j]*f[i-j]*(2^j-1)}
]
注意到 (n) 的范围为50的时候答案会超long long,所以要写高精,这道题从补集的角度考虑有另外的一个递推式:
[F[i]=2^{i*(i-1)/2}-sum_{j=1}^{i-1}{F[j]*C_{i-1}^{j-1}*2^{(i-j)*(i-j-1)/2}}
]
不过这个式子实现时比前者要多写一个减法的高精,同时 (2^{i*(i-1)/2}) 也要高精预处理,所以写起来稍微麻烦一点。
Code:
#include<iostream>
#include<cstring>
#define N 51
using namespace std;
struct Big_int{
int p[600],siz;
Big_int(){siz=0;}
void print(){
for(int i=siz-1;i>=0;i--)cout<<p[i];
cout<<"
";
}
Big_int operator +(const Big_int b)const{
Big_int ret;ret.siz=max(siz,b.siz);
memset(ret.p,0,sizeof(ret.p));
for(int i=0;i<ret.siz;i++){
ret.p[i]+=p[i]+b.p[i];
if(ret.p[i]>=10){
ret.p[i]-=10,ret.p[i+1]+=1;
ret.siz=max(ret.siz,i+2);
}
}
return ret;
}
Big_int operator *(const Big_int b)const{
Big_int ret;
memset(ret.p,0,sizeof(ret.p));
for(int i=0;i<siz;i++){
for(int j=0;j<b.siz;j++)
ret.p[i+j]+=p[i]*b.p[j];
}
for(int i=0;i<b.siz+siz;i++){
if(ret.p[i])ret.siz=max(ret.siz,i+1);
}
for(int i=0;i<ret.siz;i++){
if(ret.p[i]>=10){
ret.p[i+1]+=ret.p[i]/10,ret.p[i]%=10;
ret.siz=max(ret.siz,i+2);
}
}
return ret;
}
Big_int operator / (const int b)const{
Big_int ret;
int left=0;
for(int i=siz-1;i>=0;i--){
left=left*10+p[i];
if(ret.siz==0&&(left/b)==0)continue;
if(ret.siz==0)ret.siz=i+1;
ret.p[i]=left/b,left%=b;
}
return ret;
}
}f[N],pow[N],one;
Big_int turn(long long k){
Big_int ret;
while(k)ret.p[ret.siz++]=k%10,k/=10;
return ret;
}
int n;
Big_int C(int n,int m){
Big_int ret=one;
for(int i=n,j=1;j<=m;i--,j++)
ret=(ret*turn(i))/j;
return ret;
}
void init(){
one.siz=1,one.p[0]=1;
f[1]=f[2]=one;
for(int i=3;i<=50;i++){
for(int j=1;j<i;j++)
f[i]=f[i]+C(i-2,j-1)*f[j]*f[i-j]*turn((1ll<<j)-1);
}
}
int main(){
init();
while(cin>>n&&n)f[n].print();
return 0;
}