[CQOI2017] 老C的键盘(树形dp + 组合数)

problem

luogu-P3757

solution

observation : \text{observation}: observation: h i / 2 − h i h_{i/2}-h_i hi/2hi 的大小关系,其实就是个二叉树的大小关系。

这很类似之前的排列 d p dp dp ,迁移过来,我们尝试 f ( i , j ) : i f(i,j):i f(i,j):i 在子树内第 j j j 小的方案数。

当所有符号都是一个方向的时候,就是 [ZJOI2010]排列计数的题解,所以我们类比这里应该也是有组合数计算转移的。

同样是编号划分离散化的理解。

假设当前点为 u u u,儿子为 v v v,枚举 u u u 为其所在连通块的第 i i i 小, v v v 为其所在联通块的第 j j j 小:

  • h u < h v h_u<h_v hu<hv

    考虑合并后, u u u 可能在新大小为 s i z u + s i z v siz_u+siz_v sizu+sizv 的连通块中的排名。

    这就要看 v v v 所在联通块前 j j j 小的数与 h u h_u hu 的关系了。

    但一定不会到 i + j i+j i+j

    所以,枚举合并后 u u u 的排名为 k k k i ≤ k < i + j i\le k<i+j ik<i+j

    f ( u , k ) ← f ( u , i ) ∗ f ( v , j ) ∗ ( k − 1 i − 1 ) ∗ ( s i z u + s i z v − k s i z u − i ) f(u,k)\leftarrow f(u,i)*f(v,j)*\binom{k-1}{i-1}*\binom{siz_u+siz_v-k}{siz_u-i} f(u,k)f(u,i)f(v,j)(i1k1)(sizuisizu+sizvk)

  • h u > h v h_u>h_v hu>hv

    转移方程式和原理同上一种情况,只是 k k k 的范围有所变化, i + j ≤ k ≤ i + s i z v i+j\le k\le i+siz_v i+jki+sizv

时间复杂度为 O ( n 3 ) O(n^3) O(n3)

前缀和优化 O ( n 2 ) O(n^2) O(n2) 可以看[HEOI2013]SAO的题解

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 1000000007
#define maxn 105
char s[maxn];
int n;
int c[maxn][maxn], f[maxn][maxn], g[maxn], siz[maxn];

void dfs( int u ) {
	f[u][1] = siz[u] = 1;
	for( int o = 0;o <= 1;o ++ ) {
		int v = (u << 1) + o;
		if( v > n ) break;
		dfs( v );
		memcpy( g, f[u], sizeof( f[u] ) );
		memset( f[u], 0, sizeof( f[u] ) );
		for( int i = 1;i <= siz[u];i ++ )
			for( int j = 1;j <= siz[v];j ++ )
				if( s[v] == '>' ) {
					for( int k = i + j;k <= i + siz[v];k ++ )
						(f[u][k] += g[i] * c[k - 1][i - 1] % mod * f[v][j] % mod * c[siz[u] + siz[v] - k][siz[u] - i]) %= mod;
				}
				else {
					for( int k = i;k < i + j;k ++ )
						(f[u][k] += g[i] * c[k - 1][i - 1] % mod * f[v][j] % mod * c[siz[u] + siz[v] - k][siz[u] - i]) %= mod;
				}
		siz[u] += siz[v];
	}
}

signed main() {
	scanf( "%lld %s", &n, s + 2 );
	for( int i = 0;i <= n;i ++ ) {
		c[i][0] = c[i][i] = 1;
		for( int j = 1;j < i;j ++ )
			c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
	}
	dfs( 1 );
	int ans = 0;
	for( int i = 1;i <= n;i ++ ) (ans += f[1][i]) %= mod;
	printf( "%lld\n", ans );
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值