【BZOJ3167】SAO(HEOI2013)-高维树形DP+组合数学

测试地址:SAO
题目大意: 给定一棵树边是有向边的树(边不一定从根指向儿子),求拓扑序数量。 n ≤ 1000 n\le 1000 n1000
做法: 本题需要用到高维树形DP+组合数学。
如果边都是从根连向儿子的话,简单组合就可以树形DP O ( n ) O(n) O(n)做了,问题是这道题并不是这样的,而是一个“类树”的结构,但因为它还是棵树所以我们还是考虑树形DP。
考虑用类似树形背包的做法,把子树一一合并到根上。令 f ( i , j ) f(i,j) f(i,j)为在以 i i i为根的子树中,点 i i i在拓扑序中的位置为 j j j的拓扑序方案数,那么我们一开始令 f ( i , 1 ) = 1 f(i,1)=1 f(i,1)=1,其它都是 0 0 0。那么在合并一棵子树时,这棵子树的根要求比点 i i i先取或者后取,因为这两个限制对称,所以在这里我们只讨论要求子树根比点 i i i先取的情况。
考虑枚举两个值 j , k j,k j,k j j j表示点 i i i在合并前的拓扑序中的位置, k k k表示在当前子树的拓扑序中比点 i i i先取的点的数目,我们来考虑贡献。首先,因为满足性质的拓扑序可以随便选,因此贡献乘上一个 f ( i , j ) f(i,j) f(i,j)。接着,因为要求子树根要比点 i i i先取,所以当且仅当 p ≤ k p\le k pk p p p表示子树根在当前子树的拓扑序中的位置)时才满足条件,此时所有的 f ( s o n , p ) f(son,p) f(son,p)都是可以选的,那么贡献乘上一个 ∑ p = 1 k f ( s o n , p ) \sum_{p=1}^k f(son,p) p=1kf(son,p)。接着就是合并的环节了,当前子树拓扑序的前 k k k个塞到点 i i i之前,方案数为 C j + k − 1 k C_{j+k-1}^k Cj+k1k,剩下的 s i z ( s o n ) − k siz(son)-k siz(son)k个塞到点 i i i之后,同理得到方案数为 C s i z ( i ) + s i z ( s o n ) − j − k s i z ( s o n ) − k C_{siz(i)+siz(son)-j-k}^{siz(son)-k} Csiz(i)+siz(son)jksiz(son)k。那么令 n o w ( x ) now(x) now(x)为合并后拓扑序中点 i i i在第 x x x位的方案数,那么一对 j , k j,k j,k n o w ( j + k ) now(j+k) now(j+k)的贡献为:
f ( i , j ) ⋅ [ ∑ p = 1 k f ( s o n , p ) ] ⋅ C j + k − 1 k ⋅ C s i z ( i ) + s i z ( s o n ) − j − k s i z ( s o n ) − k f(i,j)\cdot [\sum_{p=1}^k f(son,p)]\cdot C_{j+k-1}^k\cdot C_{siz(i)+siz(son)-j-k}^{siz(son)-k} f(i,j)[p=1kf(son,p)]Cj+k1kCsiz(i)+siz(son)jksiz(son)k
类似地处理完要求 s o n son son i i i后取的情况,预处理组合数,再顺便算一个前缀和(要求那个 ∑ \sum )就可以很快地直接DP了。而因为这个DP合并子树的方式和树形背包极其相似,我们可以用类似树形背包的方法证明复杂度是 O ( n 2 ) O(n^2) O(n2)的(考虑每对点都在其LCA处恰好被计算一次),我个人把这种高维树形DP称为类树形背包。这样我们就解决了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int T,n,first[1010],tot;
int siz[1010];
ll C[1010][1010],f[1010][1010],sum[1010][1010],now[1010];
struct edge
{
	int v,next;
	bool type;
}e[2010];

void insert(int a,int b,bool type)
{
	e[++tot].v=b;
	e[tot].type=type;
	e[tot].next=first[a];
	first[a]=tot;
}

ll CC(int n,int m)
{
	if (n<0||m<0||n<m) return 0;
	else return C[n][m];
}

void dp(int v,int fa)
{
	siz[v]=1;
	memset(f[v],0,sizeof(f[v]));
	f[v][1]=1;
	for(int i=first[v];i;i=e[i].next)
		if (e[i].v!=fa)
		{
			int y=e[i].v;
			dp(y,v);
			for(int j=1;j<=siz[v]+siz[y];j++)
				now[j]=0;
			if (e[i].type)
			{
				for(int j=1;j<=siz[v];j++)
					for(int k=1;k<=siz[y];k++)
						now[j+k]=(now[j+k]+f[v][j]*sum[y][k]%mod*CC(j+k-1,k)%mod*CC(siz[v]+siz[y]-j-k,siz[y]-k))%mod;
			}
			else
			{
				for(int j=1;j<=siz[v];j++)
					for(int k=0;k<siz[y];k++)
						now[j+k]=(now[j+k]+f[v][j]*(sum[y][siz[y]]-sum[y][k]+mod)%mod*CC(j+k-1,k)%mod*CC(siz[v]+siz[y]-j-k,siz[y]-k))%mod;
			}
			siz[v]+=siz[y];
			for(int j=1;j<=siz[v];j++)
				f[v][j]=now[j];
		}
	sum[v][0]=0;
	for(int i=1;i<=siz[v];i++)
		sum[v][i]=(sum[v][i-1]+f[v][i])%mod;
}

int main()
{
	C[0][0]=1;
	for(int i=1;i<=1000;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	
	scanf("%d",&T);
	while(T--)
	{
		memset(first,0,sizeof(first));
		tot=0;
		
		scanf("%d",&n);
		for(int i=1;i<n;i++)
		{
			int a,b;
			char s;
			scanf("%d %c%d",&a,&s,&b);
			if (s=='>') swap(a,b);
			insert(a,b,0);
			insert(b,a,1);
		}
		
		dp(0,-1);
		printf("%lld\n",sum[0][n]);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值