CF1528E-Mashtali and Hagh Trees——DP计数

E - Mashtali and Hagh Trees

题目描述

定义一个Hagh树为满足以下三个条件的有向树:

  • 最长的有向路径长度恰好为 n n n
  • 每个节点的度最多为 3 3 3
  • 定义节点 u u u v v v 为朋友节点当且仅当存在一条从 u u u v v v 的有向路径,则任意两个节点 u u u v v v 要么为朋友节点,要么存在一个节点 w w w u u u w w w v v v w w w 为朋友节点。

给定 n n n ,求有多少个不同的合法无标号Hagh树。

数据范围与提示

1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106

思路

显然,这三个条件少了任意一个,方案数都为无穷大,不好通过容斥减少条件。

所以我们的核心思想是把答案分情况来求。

首先我们看到第三个条件很奇怪,于是就用它来推一推性质:

考虑有两个点 u u u v v v 不为朋友节点,它们同存在一条路径指向 w w w

u—>w<—v

那么有一点可以确定, u u u v v v 的出度一定为1,也就是它们不会再指向其它节点。因为如果 u u u 指向除 w w w 外的任意一点,那么那个点和 v v v 就不满足要求,换成 v v v 同理。

由此我们知道该树有一个很重要的性质:如果有一个点的入度大于1,那么有路径指向它的所有节点出度为1。同理,如果有一个点的出度大于1,那么它所指向的所有节点入度为1。

根据此性质我们发现,一个Hagh树只可能有三种情况:

①一棵由父节点指向儿子节点的有向边构成的树、②一棵由儿子节点指向父节点的有向边构成的树,以及③这样两棵树由一条长度至少为1的路径相连。

第三种情况示例(图片摘自 别人的题解 )为避免重复,规定第③种中的两棵树的根节点儿子数为2,①②两种的根节点儿子数不能为1。

然后我们就可以用 DP 方法把三种情况逐一击破。

具体地,设 d p i dp_i dpi 为满足以下条件的Hagh树的数量。

  • 树中存在一个根,根节点最多两个儿子;
  • 所有边方向都从父节点指向儿子节点;
  • 从根到叶节点路径长度不超过 i i i

d p i dp_i dpi 时,分成根节点有0、1、2个儿子三种情况来求,则有
d p i = 1 + d p i − 1 + d p i − 1 ∗ ( d p i − 1 + 1 ) 2 dp_i=1+dp_{i-1}+\frac{dp_{i-1}*(dp_{i-1}+1)}{2} dpi=1+dpi1+2dpi1(dpi1+1)
然后我们通过差分定义 d p 1 dp1 dp1 d p 2 dp2 dp2

  • d p 1 i = d p i − d p i − 1 dp1_i=dp_i-dp_{i-1} dp1i=dpidpi1 ,表示最长的有向路径长度不超过 i i i 的根节点恰好有2个儿子的方案数,或最长的有向路径长度恰好为 i i i 的根节点有最多2个儿子的方案数。 d p 1 dp1 dp1 是个很好的工具,因为它同时有这两种定义。
  • d p 2 i = d p 1 i − d p 1 i − 1 dp2_i=dp1_i-dp1_{i-1} dp2i=dp1idp1i1 , 表示最长的有向路径长度恰好为 i i i 的根节点恰好有2个儿子的方案数。此处利用 d p 1 dp1 dp1 的第一条定义差分。

至此我们的工具已经足够,可以开始解决Hagh树的三种情况。

第①、②种情况是一一对应的,所以合并为一种情况来讨论:

  • 根节点有2个儿子,答案为 2 ∗ ( d p n − 1 ∗ ( d p n − 1 + 1 ) 2 − d p n − 2 ∗ ( d p n − 2 + 1 ) 2 ) 2*(\frac{dp_{n-1}*(dp_{n-1}+1)}{2}-\frac{dp_{n-2}*(dp_{n-2}+1)}{2}) 2(2dpn1(dpn1+1)2dpn2(dpn2+1))
  • 根节点有3个儿子,答案为 2 ∗ ( d p n − 1 ∗ ( d p n − 1 + 1 ) ∗ ( d p n − 1 + 2 ) 6 − d p n − 2 ∗ ( d p n − 2 + 1 ) ∗ ( d p n − 2 + 2 ) 6 ) 2*(\frac{dp_{n-1}*(dp_{n-1}+1)*(dp_{n-1}+2)}{6}-\frac{dp_{n-2}*(dp_{n-2}+1)*(dp_{n-2}+2)}{6}) 2(6dpn1(dpn1+1)(dpn1+2)6dpn2(dpn2+1)(dpn2+2))

对于第③种,枚举其中一棵树的深度,答案为
∑ i = 0 n − 1 d p 2 i ∗ d p 1 n − i − 1 \sum_{i=0}^{n-1}dp2_i*dp1_{n-i-1} i=0n1dp2idp1ni1此处利用 d p 1 dp1 dp1 的第二条定义。

最后汇总所有答案,复杂度 O ( n ) O(n) O(n)

代码

#include<cstdio>//JZM YYDS!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#define ll long long
#define MAXN 1000005
#define uns unsigned
#define MOD 998244353ll
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
int n,m;
ll dp[MAXN],dp1[MAXN],dp2[MAXN],ans;
ll iv2=(MOD+1)>>1,iv3=(MOD+1)/3;
signed main()
{
	n=read();
	dp[0]=dp1[0]=dp2[0]=1;
	for(int i=1;i<=n;i++){
		dp[i]=(1+dp[i-1]+dp[i-1]*(dp[i-1]+1)%MOD*iv2%MOD)%MOD;
		dp1[i]=(dp[i]-dp[i-1]+MOD)%MOD;
		dp2[i]=(dp1[i]-dp1[i-1]+MOD)%MOD;
	}
	ans=(ans+dp[n-1]*(dp[n-1]+1)%MOD*(dp[n-1]+2)%MOD*iv3%MOD)%MOD;
	if(n>1)ans=(ans-dp[n-2]*(dp[n-2]+1)%MOD*(dp[n-2]+2)%MOD*iv3%MOD+MOD)%MOD;
	ans=(ans+dp[n-1]*(dp[n-1]+1)%MOD)%MOD;
	if(n>1)ans=(ans-dp[n-2]*(dp[n-2]+1)%MOD+MOD)%MOD;
	for(int i=0;i<n;i++)ans=(ans+dp2[i]*dp1[n-i-1]%MOD)%MOD;
	printf("%lld\n",ans);
	return 0;
}

关于代码开头的注释:每次加了这条程序就快了一倍,很神奇
建议大家也试试看

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值