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

Address

洛谷P4099
BZOJ3167

Solution

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

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;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/83144634
今日推荐