【计数类DP】POJ 1737

链接

http://poj.org/problem?id=1737


大意

给定 n n n个点的无向联通图有多少个,节点有标号


思路

首先 x j b xjb xjb乱想,我们就能想到 d p dp dp,然后这题显然是一个计数类 d p dp dp

因为是有标号的,所以一定和组合数有关,先算出组合再说

n n n个点的无向图总数显然是
2 n × ( n − 1 ) 2 2^{\frac{n\times(n-1)}{2}} 22n×(n1)

因为 N N N个点的无向图最多有 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n1)条边,每条边可有可无,所以是这个数

接下来计算 n n n个点不连通的数量,一张不连通的无向图必定有若干个联通快构成,所以,我们有 C n − 1 k − 1 C_{n-1}^{k-1} Cn1k1中选法。剩余 n − k n-k nk个节点勾成任意

无向图,有 2 ( N − k ) × ( n − k − 1 ) 2 2^{\frac{(N-k)\times(n-k-1)}{2}} 22(Nk)×(nk1)种方法

就得到了方程
f [ i ] = 2 i × ( i − 1 ) 2 − ∑ j = 1 i − 1 f [ j ] × C i − 1 j − 1 × 2 ( i − j ) × ( i − j − 1 ) 2 f[i]=2^{\frac{i\times(i-1)}{2}}-\sum_{j=1}^{i-1}f[j]\times C_{i-1}^{j-1}\times 2^{\frac{(i-j)\times(i-j-1)}{2}} f[i]=22i×(i1)j=1i1f[j]×Ci1j1×22(ij)×(ij1)

由于数据太大,要用高精度,然后再打一个压位高精即可,因为我们不确定高精乘法的位数,所以我们可以用vector去模拟高精乘


代码

#include<cstdio>
#include<vector>
#include<algorithm>
#define ymw 10000
using namespace std;
typedef unsigned long long ull;
int n;
vector<ull>tri[50],c[50],f[50];
inline void write(ull x){if(x>9)write(x/10);putchar(x%10+48);}//输出优化
int main()
{
	for(register int i=0;i<50;i++) c[i].push_back(1);//一开始每个数默认为1
	for(register int i=0;i<50;i++)
		for(register int j=1;j<=i+1>>1;j++) c[i].push_back(c[i][j-1]*(i-j+2)/j);//算组合数
    tri[0].push_back(1);//表示一张图中无向图的个数
	for(register int i=1;i<50;i++)
	{
		tri[i]=tri[i-1]; ull g=0,s;
        for(register int j=0;j<tri[i].size();j++)//高精乘单精
		{
            s=tri[i][j]*(1ll<<i)+g;
            g=s/ymw; tri[i][j]=s%ymw;
        }
        while (g) tri[i].push_back(g%ymw),g/=ymw;//处理进位
	}
	f[0].push_back(1);//初始化f
	for(register int i=1;i<50;i++)
	{
		f[i]=tri[i];//一开始就是这个玩意
		for(register int j=0;j<i;j++)
		{
			vector<ull>t,t1; t=tri[i-j-1]; t1.clear(); ull g=0,s;
            for(register int p=0;p<t.size();p++)//然后高精乘单精
			{
                s=t[p]*c[i-1][min(j,i-j)]+g;
                g=s/ymw; t[p]=s%ymw;
            }
            while(g)t.push_back(g%ymw),g/=ymw;
            for(register int p1=0;p1<t.size();p1++)
            for(register int p2=0;p2<f[j].size();p2++)//高精乘高精
			{
            	if(t1.size()==p1+p2) t1.push_back(t[p1]*f[j][p2]);else t1[p1+p2]+=t[p1]*f[j][p2];
            	if(t1.size()==p1+p2+1) t1.push_back(t1[p1+p2]/ymw);else t1[p1+p2+1]+=t1[p1+p2]/ymw;
            	t1[p1+p2]%=ymw;
			}
			g=0;
			while(t1.size()<f[i].size()) t1.push_back(0);//记得这里要补0,不然访问无效内存
			for(register int p=0;p<f[i].size();p++)//高精减高精 
			if(f[i][p]>=t1[p]+g) f[i][p]-=t1[p]+g,g=0;else f[i][p]+=ymw-t1[p]-g,g=1;
		}
	}
	while(scanf("%d",&n)==1&&n)//输入
	{
		for (register int j=f[n-1].size()-1;j>=0;j--)
		{
			ull k=ymw/10; if (j<f[n-1].size()-1)
			while(k>f[n-1][j]&&k>1) putchar('0'),k/=10;//输出
			write(f[n-1][j]);
		}
		putchar(10);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值