2018.10.14 noip模拟赛 超级树

15 篇文章 0 订阅
5 篇文章 0 订阅

题意:给定一个深度为k的满二叉树,将他的所有节点向这个点的所有祖宗连边,求新生成的这样一棵树中经过一个点最多的路径数目

怎么搞?

显然是个递推嘛...

记状态f[i][j]代表二叉树的深度为i,从中选出j条互不相交的路径的方案数(即这些路径不会经过同一个点超过一次)

然后呢?

我们会发现,由i变成i+1时,超级树的变化就相当于将两棵深度为i的超级树连到一个根上,然后将所有点与根连边即可

据此,我们设计转移:

记状态f[i][l]表示深度为i+1的超级树的左子树中选出l条不相交的路径,f[i][r]表示深度为i+1的右子树中选出r条不相交的路径的方案数

则我们可以分类转移出f[i+1]:

①:根节点不参与转移:

f[i+1][l+r]+=f[i][l]*f[i][r]

②:根节点一个点作为一条新的路径:

f[i+1][l+r+1]+=f[i][l]*f[i][r](即左右子树正常选,加上根节点自己一条路径)

③:根节点与左右子树中某一条边连接(由于根节点与所有节点有连边,所以根节点可以与所有路径相连)

f[i+1][l+r]+=2*f[i][l]*f[i][r]*(l+r)(选择根节点与哪条路径相连,同时根节点可以与路径首尾分别相连)

④:根节点连接了左右子树中各一条边(即左右子树通过根节点连出了一条路径)

f[i+1][l+r-1]+=2*f[i][l]*f[i][r]*l*r(根节点可以选择与左边某条路径和右边某条路径连边,选法有lr种,*2的原因同上)

⑤:根节点连接了左子树或右子树中两条边(即左子树中两条边通过根节点相连或右子树中两条边通过根节点相连)

f[i+1][l+r-1]+=2*f[i][l]*f[i][r]*(l*(l-1)/2+r*(r-1)/2)(即从左子树中选出两条边或从右子树中选出两条边相连)

这样转移就完成了,答案即为f[k][1](即深度为k的超级树,选出一条不经过重复点的方案数)

当然,有可能会发现一个问题:这样转移的第二维大小太大了,可能达到2^k,这样显然是不合理的

但是我们会发现,所有的转移只设计l+r,l+r-1,l+r+1三者之间的关系,所以如果答案是f[k][1],那么最初需要用来转移的第二维不会超过k!

这样我们就减小了第二维的大小

这样问题也就解决了

时间复杂度O(k^3),k<=300时常数不小,需要卡卡常(直接一堆取模是行不通的了!)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
ll f[305][305];
ll k,mode;
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%I64d%I64d",&k,&mode);
	f[1][0]=f[1][1]=1;
	for(int i=2;i<=k;i++)
	{
		for(int l=0;l<=k;l++)
		{
			for(int r=0;l+r<=k;r++)
			{
				ll temp=f[i-1][l]*f[i-1][r]%mode;
				f[i][l+r]+=temp;
				if(f[i][l+r]>=mode)
				{
					f[i][l+r]-=mode;
				}
				f[i][l+r+1]+=temp;
				if(f[i][l+r+1]>=mode)
				{
					f[i][l+r+1]-=mode;
				}
				f[i][l+r]+=2*temp*(l+r)%mode;
				if(f[i][l+r]>=mode)
				{
					f[i][l+r]-=mode;
				}
				if(l+r>=1)
				{
					f[i][l+r-1]+=2*temp*l*r%mode;
					if(f[i][l+r-1]>=mode)
					{
						f[i][l+r-1]-=mode;
					}
					f[i][l+r-1]+=2*temp*(l*(l-1)/2+r*(r-1)/2)%mode;
					if(f[i][l+r-1]>=mode)
					{
						f[i][l+r-1]-=mode;
					}
				}
			}
		}
	}
	printf("%I64d\n",f[k][1]%mode);
	return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值