[BZOJ3167][Heoi2013]Sao(树形DP+组合数学)

Address

洛谷P4099
BZOJ3167

Solution

定义状态: f [ u ] [ i ] f[u][i] f[u][i] 表示 u u u 的子树内所有的点进行排列, u u u 排在位置 i i i 的方案数。
考虑按照树形背包的方式转移,设 f ′ [ u ] f'[u] f[u] 为枚举到 u u u 的子节点 v v v 之前的 DP 数组。
如何合并 f ′ [ u ] f'[u] f[u] f [ v ] f[v] f[v] 呢?
先考虑 u u u 必须排在 v v v 后面的情况:
先枚举 i i i j j j ,考虑如何从 f ′ [ u ] [ i ] f'[u][i] f[u][i] f [ v ] [ ] f[v][] f[v][] 合并到 f [ u ] [ i + j ] f[u][i+j] f[u][i+j]
(上面的 i i i 表示枚举到 u u u 的子节点 v v v 之前子树中 u u u 的排名, j j j 表示 v v v 的子树内排名在 u u u 前面的点数)。
而如果 u u u 必须排在 v v v 的后面,这就要求了能参与转移的 f [ v ] [ k ] f[v][k] f[v][k] 必须满足 k ≤ j k\le j kj
u u u 的子节点 v v v 之前子树大小为 s u ′ s'_u su v v v 的子树大小为 s v s_v sv ,如何求把 f ′ [ u ] [ i ] f'[u][i] f[u][i] f [ v ] [ k ] f[v][k] f[v][k] k ≤ j k\le j kj )合并起来的方案数呢?
这等价于把两个长度分别为 s u ′ s'_u su s v s_v sv 的序列合并成一个序列,使得新序列任意两个相同元素在原序列中的相对位置不变,并且对于 x x x x x x 为新序列中第 i i i 个来自序列 s u ′ s'_u su 的元素),必须满足 1 1 1 x x x 中恰好有 j j j 个元素来自序列 s v s_v sv
这又等价于把 s v s_v sv 个元素切割成 s u ′ + 1 s'_u+1 su+1 块(块内可以为空),满足前 i i i 块里恰好有 j j j 个元素。
根据组合数学的知识得到这样的方案数为:
C i + j − 1 i − 1 × C s u ′ − i + s v − j s u ′ − i C_{i+j-1}^{i-1}\times C_{s'_u-i+s_v-j}^{s'_u-i} Ci+j1i1×Csui+svjsui
所以转移:
f [ u ] [ i + j ] + = C i + j − 1 i − 1 × C s u ′ − i + s v − j s u ′ − i × f ′ [ u ] [ i ] × ∑ k ≤ j f [ v ] [ k ] f[u][i+j]+=C_{i+j-1}^{i-1}\times C_{s'_u-i+s_v-j}^{s'_u-i}\times f'[u][i]\times \sum_{k\le j}f[v][k] f[u][i+j]+=Ci+j1i1×Csui+svjsui×f[u][i]×kjf[v][k]
u u u 排在 v v v 之前时:
f [ u ] [ i + j ] + = C i + j − 1 i − 1 × C s u ′ − i + s v − j s u ′ − i × f ′ [ u ] [ i ] × ∑ k ≥ j f [ v ] [ k ] f[u][i+j]+=C_{i+j-1}^{i-1}\times C_{s'_u-i+s_v-j}^{s'_u-i}\times f'[u][i]\times \sum_{k\ge j}f[v][k] f[u][i+j]+=Ci+j1i1×Csui+svjsui×f[u][i]×kjf[v][k]
∑ k ≤ j \sum_{k\le j} kj ∑ k ≥ j \sum_{k\ge j} kj 可以对 f f f 求前缀和得出。
由于 f [ u ] f[u] f[u] 的第二维的上界只有 s u s_u su ,所以复杂度相当于每对点都在 LCA 处贡献了 O ( 1 ) O(1) O(1) ,所以复杂度 O ( T n 2 ) O(Tn^2) O(Tn2)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

inline char get()
{
	char c;
	while ((c = getchar()) != '<' && c != '>');
	return c;
}

const int N = 3005, M = N << 1, ZZQ = 1e9 + 7;

int n, ecnt, nxt[M], adj[N], go[M], sze[N], f[N][N], C[M][M],
x[N], s[N][N], ans;
bool cm[M];

void add_edge(int u, int v, bool op)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; cm[ecnt] = op;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; cm[ecnt] = op ^ 1;
}

int orz(int n, int m, int x, int y)
{
	return 1ll * C[x + y - 1][x - 1] * C[n - x + m - y][n - x] % ZZQ;
}

void dfs(int u, int fu)
{
	int i, j;
	sze[u] = f[u][1] = 1;
	Tree(u)
	{
		dfs(v, u);
		For (i, 0, sze[u] + sze[v]) x[i] = 0;
		For (i, 1, sze[u]) For (j, 0, sze[v])
			x[i + j] = (1ll * f[u][i] * (cm[e] ? s[v][sze[v]] -
				s[v][j] + ZZQ : s[v][j]) % ZZQ
					* orz(sze[u], sze[v], i, j) + x[i + j]) % ZZQ;
		For (i, 1, sze[u] + sze[v]) f[u][i] = x[i];
		sze[u] += sze[v];
	}
	For (i, 1, sze[u]) s[u][i] = (s[u][i - 1] + f[u][i]) % ZZQ;
}

void work()
{
	int i, x, y; char op;
	ecnt = ans = 0;
	n = read();
	For (i, 1, n) adj[i] = 0;
	For (i, 1, n - 1)
		x = read() + 1, op = get(), y = read() + 1,
		add_edge(x, y, op == '<' ? 1 : 0);
	dfs(1, 0);
	For (i, 1, n) ans = (ans + f[1][i]) % ZZQ;
	printf("%d\n", ans);
}

int main()
{
	int i, j, T = read();
	For (i, 0, 1000) C[i][0] = 1;
	For (i, 1, 1000) For (j, 1, i)
		C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % ZZQ;
	while (T--) work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值