计树 【计数(树)dp】

在这里插入图片描述
在这里插入图片描述

询问方案数,考虑计数 d p dp dp 。不过如果直接进行 d p dp dp ,对于一个点 x x x 的儿子约束是 ∃ s o n x > x \exists son_x>x sonx>x ,也就是说儿子编号可以有大有小,不好维护。我们尝试先只连一颗树里面满足 s o n x > x son_x>x sonx>x 的边,这样会形成一片森林(假设森林里有 x x x 颗树),然后我们再把其中 x − 1 x-1 x1 颗树连到钦定的主树的空位中。
这样转化有什么好处?我们把儿子的限制变成了一个,对于这个限制,我们从前往后枚举时只要记下前面的空位就保证了顺序,而对于子树的合并,并无限制,只要找到空位即可,显然比直接转移考虑两个状态要简单的多。
我们设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示处理了前 i i i 个点,现在有 j j j 颗子树,前面有 k k k 个空位被钦定要填儿子的方案数。
对于点 i i i在可能的情况下,我们钦定:

  1. i i i 将有 0 0 0 个儿子, 则有 { f [ i ] [ j ] [ k ] + = f [ i − 1 ] [ j − 1 ] [ k ] (新开一颗子树) f [ i ] [ j ] [ k ] + = ( k + 1 ) ∗ f [ i − 1 ] [ j ] [ k + 1 ] (选择一个空位填进去) \begin{cases}f[i][j][k]+=f[i-1][j-1][k](新开一颗子树)\\f[i][j][k]+=(k+1)*f[i-1][j][k+1](选择一个空位填进去)\end{cases} {f[i][j][k]+=f[i1][j1][k](新开一颗子树)f[i][j][k]+=(k+1)f[i1][j][k+1](选择一个空位填进去)
  2. i i i 将有 1 1 1 个儿子,则有 { f [ i ] [ j ] [ k ] + = 2 ∗ f [ i − 1 ] [ j − 1 ] [ k − 1 ] (新开一颗子树) f [ i ] [ j ] [ k ] + = 2 ∗ k ∗ f [ i − 1 ] [ j ] [ k ] (选择一个空位填进去) \begin{cases}f[i][j][k]+=2*f[i-1][j-1][k-1](新开一颗子树)\\f[i][j][k]+=2*k*f[i-1][j][k](选择一个空位填进去)\end{cases} {f[i][j][k]+=2f[i1][j1][k1](新开一颗子树)f[i][j][k]+=2kf[i1][j][k](选择一个空位填进去) 因为这一个空位可以选择是左儿子的空位还是右儿子的空位,所以要乘上系数 2 2 2
  3. i i i 将有 2 2 2 个儿子,则有 { f [ i ] [ j ] [ k ] + = f [ i − 1 ] [ j − 1 ] [ k − 2 ] (新开一颗子树) f [ i ] [ j ] [ k ] + = ( k − 1 ) ∗ f [ i − 1 ] [ j ] [ k − 1 ] (选择一个空位填进去) f [ i ] [ j ] [ k ] + = 2 ∗ j ∗ f [ i − 1 ] [ j ] [ k − 1 ] (新开一颗子树的同时接一颗子树进来,因为可以选择左右儿子,所以乘上系数 2 ) f [ i ] [ j ] [ k ] + = 2 ∗ j ∗ k ∗ f [ i − 1 ] [ j + 1 ] [ k ] (选择一个空位填进去的同时接一颗子树进来,因为接的子树和空位所在的子树不可以是同一颗子树,所以乘 j ) \begin{cases}f[i][j][k]+=f[i-1][j-1][k-2](新开一颗子树)\\f[i][j][k]+=(k-1)*f[i-1][j][k-1](选择一个空位填进去)\\f[i][j][k]+=2*j*f[i-1][j][k-1](新开一颗子树的同时接一颗子树进来,因为可以选择左右儿子,所以乘上系数 2 )\\f[i][j][k]+=2*j*k*f[i-1][j+1][k](选择一个空位填进去的同时接一颗子树进来,因为接的子树和空位所在的子树不可以是同一颗子树,所以乘j)\end{cases} f[i][j][k]+=f[i1][j1][k2](新开一颗子树)f[i][j][k]+=(k1)f[i1][j][k1](选择一个空位填进去)f[i][j][k]+=2jf[i1][j][k1](新开一颗子树的同时接一颗子树进来,因为可以选择左右儿子,所以乘上系数2f[i][j][k]+=2jkf[i1][j+1][k](选择一个空位填进去的同时接一颗子树进来,因为接的子树和空位所在的子树不可以是同一颗子树,所以乘j

起始状态: f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0]=1 f[0][0][0]=1
目标状态: f [ n ] [ 1 ] [ 0 ] f[n][1][0] f[n][1][0]
时间复杂度: O ( n 3 ) O(n^3) O(n3)

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int tid,t,n,l[310],r[310],f[310][310][610],lsum[310],rsum[310];
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&tid,&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&l[i],&r[i]);
			lsum[i]=lsum[i-1]+l[i];
			rsum[i]=rsum[i-1]+r[i];
		}
		memset(f,0,sizeof f);
		f[0][0][0]=1;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=i;j++)
			{
				for(int k=max(lsum[i]-(i-j),0);k<=rsum[i]-(i-j);k++)
				{
					if(l[i]==0)
					{
						f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k])%mod;
						f[i][j][k]=(f[i][j][k]+(long long)(k+1)*f[i-1][j][k+1]%mod)%mod;
					}
					if(l[i]<=1&&r[i]>=1)
					{
						if(k>=1)
						f[i][j][k]=(f[i][j][k]+2*f[i-1][j-1][k-1]%mod)%mod;
						f[i][j][k]=(f[i][j][k]+(long long)2*k*f[i-1][j][k]%mod)%mod;
					}
					if(r[i]==2)
					{
						if(k>=2)
						f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k-2]%mod)%mod;
						if(k>=1)
						{
							f[i][j][k]=(f[i][j][k]+(long long)(k-1)*f[i-1][j][k-1]%mod)%mod;
							f[i][j][k]=(f[i][j][k]+(long long)2*j*f[i-1][j][k-1]%mod)%mod;
						}
						f[i][j][k]=(f[i][j][k]+(long long)2*j*k*f[i-1][j+1][k]%mod)%mod;
					}
				}
			}
		}
		printf("%d\n",f[n][1][0]);
	}
	return 0;
}

总结:状态难维护时考虑转换状态,有什么需要维护的 d p dp dp 就大胆设什么状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值