询问方案数,考虑计数
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
x−1 颗树连到钦定的主树的空位中。
这样转化有什么好处?我们把儿子的限制变成了一个,对于这个限制,我们从前往后枚举时只要记下前面的空位就保证了顺序,而对于子树的合并,并无限制,只要找到空位即可,显然比直接转移考虑两个状态要简单的多。
我们设
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k] 表示处理了前
i
i
i 个点,现在有
j
j
j 颗子树,前面有
k
k
k 个空位被钦定要填儿子的方案数。
对于点
i
i
i ,在可能的情况下,我们钦定:
- 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[i−1][j−1][k](新开一颗子树)f[i][j][k]+=(k+1)∗f[i−1][j][k+1](选择一个空位填进去)
- 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]+=2∗f[i−1][j−1][k−1](新开一颗子树)f[i][j][k]+=2∗k∗f[i−1][j][k](选择一个空位填进去) 因为这一个空位可以选择是左儿子的空位还是右儿子的空位,所以要乘上系数 2 2 2 。
- 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[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)
起始状态:
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 就大胆设什么状态。