SCOI2003【严格n元树】

Description

如果一棵树的所有非叶节点都恰好有n个儿子,那么我们称它为严格n元树。如果该树中最底层的节点深度为d(根的深度为0),那么我们称它为一棵深度为d的严格n元树。例如,深度为2的严格2元树有三个,如下图:

给出n, d,编程数出深度为d的n元树数目。

Input

仅包含两个整数n, d( 0   <   n   <   =   32,   0  < =   d  < = 16)

Output

仅包含一个数,即深度为d的n元树的数目。


我是先从简单的情况入手,就讨论完全二叉树的个数,得到是2^2n  -1
然后又想完全三叉树的个数
然后就陷入了一个误区:总是想考虑下面的叶子节点该怎么加才满足,使劲推公式尽量往组合数上靠,结果憋了半天没搞出来。
然后题解是dp:
定义f[i]表示深度为i的n元树的个数,发现这个并不好直接转移
因为组合的时候不一定是一些i-1深度的n元树。
需要转化:
改为求f[i]的前缀和s[i]

s[i]表示深度不大于i的n元树的个数.
这里转移不是考虑怎么在下面加节点,而是添一个根:
s[i-1]种不同的n元树,在添一个根时候的状态是n棵不同的不超过i-1深度的树排在一起,也有可能为空.
s[i]=s[i-1]^n +1(我们考虑树不为空的情况,但组合的时候可能只有根,而s[i-1]状态里没有空的子树)
然后答案就是s[i]-s[i-1]了。

这里吸取一个教训,不要局限于题意顺序,逆向思维。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
#define base 10000
#define maxlen 101
#define get(x) (x-'0')
using namespace std;
int n,d;
const int maxn=20;
struct bign
{
	int sign,len;
	int c[maxlen];
	bign()
	{
		sign=0;
		len=1;
		memset(c,0,sizeof(c));
	}
	void Printf()
	{
		if(sign)printf("-");
		printf("%d",c[len]);
		for(int i=len-1;i>=1;i--)printf("%04d",c[i]);
		printf("\n");
	}
	void zero()
	{
		while(len-1&&!c[len])len--;
	}
	void writen(char *s)
	{
		int l=strlen(s),lim=0,k=1;
		if(s[0]=='-')
		{
			sign=1;
			lim=1;
		}
		for(int i=l-1;i>=lim;i--)
		{
			c[len]+=get(s[i])*k;
			k*=10;
			if(k==base)
			{
				len++;
				k=1;
			}
		}
	}
	void Read()
	{
		char s[maxlen*base];
		scanf("%s",s);
		writen(s);
	}
	bool operator <(const bign &b)
	{
		if(len!=b.len)return len<b.len;
		for(int i=len;i>=1;i--)
		{
			if(c[i]!=b.c[i])return c[i]<b.c[i];
		}
		return false;
	}
	bign operator =(const int &a)
	{
		char s[maxlen*base];
		sprintf(s,"%d",a);
		writen(s);
		return *this;
	}
	bign operator +(const bign &b)
	{
		bign r;
		r.len=max(len,b.len)+1;
		for(int i=1;i<=r.len;i++)
		{
			r.c[i]+=c[i]+b.c[i];
			r.c[i+1]+=r.c[i]/base;
			r.c[i]%=base;
		}
		r.zero();
		return r;
	}
	bign operator +(const int &a)
	{
		bign b;
		b=a;
		return *this+b;
	}
	bign operator *(const bign &b)
	{
		bign r;
		r.len=len+b.len+2;
		for(int i=1;i<=len;i++)
		{
			for(int j=1;j<=b.len;j++)
			{
				r.c[i+j-1]+=c[i]*b.c[j];
				r.c[i+j]+=r.c[i+j-1]/base;
				r.c[i+j-1]%=base;
			}
		}
		r.zero();
		return r;
	}
	bign operator *(const int &a)
	{
		bign b;
		b=a;
		return *this*b;
	}
	bign operator ^(const int &y)
	{
		bign res;
		bign r=*this;
		res=1;
		int t=y;
		while(t)
		{
			if(t&1)res=res*r;
			t>>=1;
			r=r*r;
		}
		return res;
	}
	bign operator -(const bign &b)
	{
		bign r=*this;
		bign y=b;
		if(r<y)swap(r,y);
		for(int i=1;i<=r.len;i++)
		{
			r.c[i]-=y.c[i];
			if(r.c[i]<0)
			{
				r.c[i]+=base;
				r.c[i+1]--;
			}
		}
		return r;
	}
}f[maxn];
//f[i]表示深度不超过i的n元树的个数 
int main()
{
	scanf("%d%d",&n,&d);
	//f[i]=f[i-1]^n+1
	f[0]=1;
	for(int i=1;i<=d;i++)f[i]=(f[i-1]^n)+1;
	if(d==0)printf("1\n");
	else
	{
		bign ans=f[d]-f[d-1];
		ans.Printf();
	}
	
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值