题解[HEOI2013]SAO.md

题意

给定一棵树,边有方向。要给点重标号,使得边都由小编号指向大编号,求重标号方案数。

n105

思路

有关树的计数。套路性考虑树形 DP。

设计状态 fu 表示 u 的子树的合法重标号方案数。如何转移?发现不好转移。

考虑什么情况下可以转移呢?发现转移是合并当前 u 子树中已经考虑了部分的状态和其某还未考虑的儿子 v 的子树的状态,而转移合法只要保证 uv 在重标号中相对大小关系符合边上的限制。那么我们考虑改状态为 fu,i 表示 u 的子树重标号且 u 的新编号为 i 的方案数。如何转移?

大于和小于的情况是对称的,所以我们只讨论 u 的新编号要小于 v 的情况,另一种情况的转移类似。我们考虑状态 fu,i 能转移到什么状态。考虑在新的重标号方案中,在 v 的子树中拿 j 个点标上小于 i+j 的编号,u 标上编号 i+j,那么考虑转移方程中需要哪些系数呢?首先,考虑 fu,i 能与哪些 fv,k 合并得到新的状态,发现 v 子树中前新编号前 j 小的点都拥有小于 i 的新编号,而 v 的新编号不能小于 u,所以要保证 k>j,也就说这次转移中合法的 v 的子树重编号方案数是 k=j+1sizvfv,k。其他系数?要在 i+j1 个新编号小于 i+j 的点中选 j 个点来自 v 的子树,这里需要乘上 (i+j1j)。新编号大于 i+j 的同理,乘上 (sizu+sizvijsizvj)。总的式子就是 fu,i+j+=(i+j1j)(sizu+sizvijsizvj)fu,ik=j+1sizvfv,k。发现前缀和可以优化掉那个 。统计答案就是 i=1nf1,i

计算一下复杂度,看似是 O(n3)?每次合并复杂度为 sizu×sizv,这与树形背包是一样的复杂度,所以总复杂度应该是 O(n2)。可以理解为 u 子树中已考虑的每个点和 v 子树中每个点都 O(1) 计算一次,发现每对点都只会在其 LCA 处计算一次,所以总复杂度是点对数即 O(n2)

实现

void DP(int u,int fa){
	sz[u]=1;
	for(int i=1;i<=n;++i)
		f[u][i]=0;
	f[u][1]=1;
	for(int ie=head[u],v=e[ie].t;ie;ie=e[ie].n,v=e[ie].t)
		if(v!=fa){
			DP(v,u);
			for(int i=1;i<=sz[u]+sz[v];++i)
				tmp[i]=0;
			if(e[ie].op)
				for(int i=1;i<=sz[u];++i)
					for(int j=1;j<=sz[v];++j)
						MAdd(tmp[i+j-1],Mul(Mul(Add(g[v][sz[v]],MOD-g[v][j-1]),f[u][i]),Mul(C(i+j-2,j-1),C(sz[u]-i+sz[v]-j+1,sz[v]-j+1))));
			else
				for(int i=1;i<=sz[u];++i)
					for(int j=1;j<=sz[v];++j)
						MAdd(tmp[i+j],Mul(Mul(g[v][j],f[u][i]),Mul(C(i+j-1,j),C(sz[u]-i+sz[v]-j,sz[v]-j))));
			sz[u]+=sz[v];
			for(int i=1;i<=sz[u];++i)
				f[u][i]=tmp[i];
		}
	for(int i=1;i<=sz[u];++i)
		g[u][i]=Add(g[u][i-1],f[u][i]);
}
posted @   Diwanul  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示