求树形图拓扑序的个数:P4099 [HEOI2013] SAO

传送门

前题提要:碰到了一道求树形图拓扑序的个数的题目,感觉十分有意思,故写一篇题解记录一下.

题目简述:给你n个点n条有向边,形成一张图,且这张图满足拓扑条件.问你这张图不同的拓扑序有几种.

考虑给的这张图是n个点以及n条边,并且满足拓扑序,也就是说,假设我们的边不是有向边的话,那么我们的这个图将形成一颗树.这个性质很重要,所以我们考虑从树形结构下手.

根据树形dp的一半套路,考虑使用 d p [ u ] dp[u] dp[u]来表示以 u u u为根节点的子树的拓扑序的个数.那么此时我们需要考虑的就是所有儿子 v v v的合并问题了(此处的合并类似与树形背包).

需要注意的是,此时我们用遍历树的方法来遍历树形图,所以u,v的边的方向我们是需要考虑的,为了讨论方便起见,此时我们先假定u需要在v前面

然后我们发现光光记录上述的个数显然是无法将 u u u v v v合并的.因为我们根本不知道每一种拓扑序的样子,光光记录个数无从下手.
考虑将 d p dp dp方程进一步扩展,使用 d p [ u ] [ i ] dp[u][i] dp[u][i]来表示以 u u u为根的子树并且u在拓扑序中第 i i i位的种类数.显然 u u u所有的拓扑序个数就是所有的 d p [ u ] [ i ] dp[u][i] dp[u][i]的和
诶,此时我们似乎可以进行合并了.
对于我们现在的 d p [ u ] [ i ] dp[u][i] dp[u][i] u u u的一个子树 v , d p [ v ] [ j ] v,dp[v][j] v,dp[v][j],我们考虑如何将其合并.显然我们可以枚举合并后 u u u在拓扑序中的位置,设合并后 u u u在第 k k k位.那么对于此次合并来说,本来在 u u u前面的点现在肯定还是在 u u u前面的,那么我们多出来的点就是原来在 v v v前面的了(注意此时我们假定u在v前面,那么v后面的肯定不可能在u前面).并且因为u,v序列各自的相对位置是不能发生改变的,我们可以使用这样的想法来统计:我们考虑将u序列的每一个数放在合并后的序列的哪一个位置.因为假设我们将u放下了,相对的v也被放下了.那么此时不难推出合并带来的贡献就是: C k − 1 i − 1 ∗ C S i z e [ u ] + S i z e [ v ] − k S i z e [ u ] − i C_{k-1}^{i-1}*C_{Size[u]+Size[v]-k}^{Size[u]-i} Ck1i1CSize[u]+Size[v]kSize[u]i,那么合并的总式子就是 d p [ u ] [ k ] + = d p [ u ] [ i ] ∗ d p [ v ] [ j ] ∗ C k − 1 i − 1 ∗ C S i z e [ u ] + S i z e [ v ] − k S i z e [ u ] − i dp[u][k]+=dp[u][i]*dp[v][j]*C_{k-1}^{i-1}*C_{Size[u]+Size[v]-k}^{Size[u]-i} dp[u][k]+=dp[u][i]dp[v][j]Ck1i1CSize[u]+Size[v]kSize[u]i
此时不难发现上述式子的朴素求法是 n 3 n^3 n3的(考虑枚举所有的 i , j , k i,j,k i,j,k).所以需要优化一下求法.
然后我们会发现上述式子其实我们可以求出所有的前缀 d p [ v ] [ j ] dp[v][j] dp[v][j],我们将原式子使用分配率搞一下,就会发现可以化解为 d p [ u ] [ k ] + = ∑ j = 1 S i z e [ v ] d p [ v ] [ j ] ∗ d p [ u ] [ i ] ∗ C k − 1 i − 1 ∗ C S i z e [ u ] + S i z e [ v ] − k S i z e [ u ] − i dp[u][k]+=\sum_{j=1}^{Size[v]}dp[v][j]*dp[u][i]*C_{k-1}^{i-1}*C_{Size[u]+Size[v]-k}^{Size[u]-i} dp[u][k]+=j=1Size[v]dp[v][j]dp[u][i]Ck1i1CSize[u]+Size[v]kSize[u]i.用这种方法来省去一层循环.所以此时我们还需要做的就是如何通过 i , k i,k i,k的范围来推出 j j j的枚举范围.手模一下,不难发现我们的 k k k存在一种范围 i ≤ k ≤ i + j − 1 i \leq k \leq i+j-1 iki+j1,然后我们考虑反推出 j ≥ k − i + 1 j \geq k-i+1 jki+1,并且此时的 k k k的枚举范围是 [ i , S i z e [ u ] + S i z e [ v ] − 1 ] [i,Size[u]+Size[v]-1] [i,Size[u]+Size[v]1].

现在考虑如果是 u u u v v v后面的情况,分析一下发现 d p dp dp方程应该和上述是基本一样的.就是枚举范围有一点变化,此时我们的 k k k的关系式是 k ≥ i + j , k ≤ i + S i z e [ v ] k \geq i+j,k \leq i+Size[v] ki+j,ki+Size[v],那么此时 i ∈ [ 1 , S i z e [ u ] ] , j ∈ [ 1 , k − i ] , k ∈ [ i + 1 , S i z e [ u ] + S i z e [ v ] ] i\in[1,Size[u]],j\in[1,k-i],k\in[i+1,Size[u]+Size[v]] i[1,Size[u]],j[1,ki],k[i+1,Size[u]+Size[v]]

至此,本题结束


下面是具体的代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long 
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
vector<int>edge[1010];int mp[1010][1010];
int dp[1010][1010],sum[1010][1010];int Size[maxn];int temp[1010];int C[1010][1010];
void dfs(int u,int per_u) {
	Size[u]=1;dp[u][1]=1;
	for(auto v:edge[u]) {
		if(v==per_u) continue;
		dfs(v,u);
		memcpy(temp,dp[u],sizeof dp[u]);
		memset(dp[u],0,sizeof dp[u]);
		if(mp[u][v]==0) {
			for(int i=1;i<=Size[u];i++) {
				for(int j=i;j<=i+Size[v]-1;j++) {
					dp[u][j]=(dp[u][j]+temp[i]*C[j-1][i-1]%mod*C[Size[u]+Size[v]-j][Size[u]-i]%mod\
					*(sum[v][Size[v]]-sum[v][j+1-i-1]+mod)%mod)%mod;
				}
			}
		}
		else {
			for(int i=1;i<=Size[u];i++) {
				for(int j=i+1;j<=i+Size[v];j++) {
					dp[u][j]=(dp[u][j]+temp[i]*C[j-1][i-1]%mod*C[Size[u]+Size[v]-j][Size[u]-i]%mod\
					*sum[v][j-i]%mod)%mod;
				}
			}
		}
		Size[u]+=Size[v];
	}
	for(int i=1;i<=Size[u];i++) {
		sum[u][i]=(sum[u][i-1]+dp[u][i])%mod;
	}
}
void init() {
	memset(dp,0,sizeof dp);memset(sum,0,sizeof sum);memset(Size,0,sizeof Size);
	memset(temp,0,sizeof temp);
}
signed 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;
		}
	}
	int T=read();
	while(T--) {
		init();
		int n=read();
		for(int i=1;i<=n;i++) edge[i].clear();
		for(int i=1;i<=n-1;i++) {
			int u=read();char c;cin>>c;int v=read();u++;v++;
			if(c=='<') mp[u][v]=0,mp[v][u]=1;
			else mp[u][v]=1,mp[v][u]=0;
			edge[u].push_back(v);
			edge[v].push_back(u);
		}
		dfs(1,0);
		cout<<sum[1][Size[1]]<<endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值