[CF1528E]Mashtali and Hagh Trees
壹、题目描述 ¶
贰、题解 ¶
观察这种特别的树的形态。
不难发现,最理想的情况是,存在一个根,使得所有树上的边的方向都是形如从根往下指或者从子节点往根指。
我们把这样的树称为 “妙妙树”,举个例子:
但是,除了妙妙树肯定还有其他情况,再来个例子:
如果我们将 \(1\) 看成树的根,那么,不难发现,从 \(4\) 开始,它的方向就和我们想要的子树方向是相反的,但是,十分烦人的,不妙树也是满足条件的。为了方便表述,我们将 \(4\) 这样的点称为 伪根,或者说,反叛的头领?
我们考虑将不妙树和妙妙树分开算,先看看妙妙树:
定义 \(dp_i\) 表示满足 \(n=i\) 的妙妙树个数。
可以转移:
几个部分:一个是 \(dp_{i-1}\),表示一个子树;对于 \(dp_{i−1} \cdot pdp_{i−2}\),表示一颗子树深度 \(i-1\),另一颗子树深度不超过 \(i-2\);
对于深度相同的部分,我们特殊说明:
假设 \(dp_{i-1}=k\),那么,当我们左子树选择第一种的时候,右子树可以选择 \(1,2,3,...,k\) 号不同的方案,共 \(k\) 种。
当左子树选择第二种,右子树如果仍然选择一号方案,那么和 “左子树为一号方案,右子树为二号方案” 同构了,这样不行,所以我们应该选择 \(2,3,4,...,k\) 号方案,共 \(k-1\) 种
同理,左子树选择三号、四号...也有同样道理,也就是说,左右子树深度相同时,方案数为
最后一步就是等差数列咯。
进行如是转移,可以 \(\mathcal O(n)\) 得到 \(dp\) 数组。
最后的答案:
不难发现,答案的形式和转移的形式有点类似,它其实分成多个部分:
- 只有一个、两个儿子的情况,\(dp_n\);
- 有三个儿子的情况:
-
- 一个儿子的深度为 \(n-1\),使用上面的分析得到 \(dp_{n-1}{pdp_{n-2}(pdp_{n-2}+1)\over 2}\),\(dp_{n-1}\) 可以直接乘,因为深度不同,方案肯定不同,不同深度之间没有必要去重;
- 有两个儿子深度为 \(n-1\),与上面的情况类似,\(pdp_{n-2}{dp_{n-1}(dp_{n-1}+1)\over 2}\);
- 有三个儿子的深度为 \(n-1\),考虑当第一个子树选择第 \(i\) 号方案,第二个子树选择 \(j(j\ge i)\) 号方案,第三个子树就只能选择 \(j,j+1,...,dp_{n-1}\) 号方案,所以这种情况的方案数为
至于乘 \(2\),因为妙妙树可以选择两个方向(向上或者向下)。而 \(-1\) 则是因为妙妙树变成链的情况(出现在只有一个儿子时)不论哪个方向都是同构的,减去一次。
现在考虑不妙树的数量。考察伪根的数量?至少得有一个吧,能否有两个?
如果有两个,则有至少两个点和整棵树的大势相反,不难发现,这两个点必然不能成为朋友。
特别进行说明,当两个伪根都是根的子节点时,它们肯定是可以互相到达的,但是这时的伪根已经不是伪根了,它成为了这棵树的“大势”,换句话说,根的更多的子树的方向和他们相同,而剩下的那个点反而成为了伪根。所以这种情况不考虑。
那么,现在我们发现只有一个伪根了,但是它在哪里?我们不妨将伪根的父亲当作根,显然,如果这个根的儿子除了伪根以外只有一个的话,那么大事不好了,大势居然和伪根的实力势均力敌,这个时候到底谁才是伪根?为了让大势固定,我们必须得让这个根的大势方向的儿子有两个,那么我们考虑一下这样的组合:一个伪根儿子+正好两个大势儿子。
实际上,这个根和两个大势儿子,其实组合成了一个妙妙树,所以我们实际上要算的就是,一个伪根树与一个根恰好有两个儿子的妙妙树的组合。
一个根恰好有两个儿子的妙妙树的情况怎么算?考虑上面使用的 \(dp\) 数组,如果定义 \(dp2_i\) 表示根刚好有两个儿子的妙妙树,那么就有 \(dp2_i=dp_i-dp_{i-1}\).
我们枚举伪根树的深度,和求得的妙妙树数量进行组合即可,即这部分的数量为
至于减一,是减去只有一个点的情况,这个时候我们算的这种情况,这个伪根可以直接成为原树的根而合法,即这种情况实际上算的是妙妙树,而并非不妙树。
叁、参考代码 ¶
#include<cstdio>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int mod=998244353;
const int maxn=1e6;
const int inv2=499122177;
const int inv6=166374059;
int dp[maxn+5], dp2[maxn+5], pdp[maxn+5];
int n;
signed main(){
n=readin(1);
if(n==1) return writc(5), 0;
dp[0]=pdp[0]=1;
rep(i, 1, n){
if(i==1) dp[i]=2;
else dp[i]=(dp[i-1]+1ll*dp[i-1]*pdp[i-2]%mod+1ll*dp[i-1]*(dp[i-1]+1)%mod*inv2%mod)%mod;
pdp[i]=(pdp[i-1]+dp[i])%mod;
}
rep(i, 1, n) dp2[i]=(dp[i]+mod-dp[i-1])%mod;
ll ans=dp[n];
ans=(ans+1ll*dp[n-1]*pdp[n-2]%mod*(pdp[n-2]+1)%mod*inv2%mod)%mod;
ans=(ans+1ll*pdp[n-2]*dp[n-1]%mod*(dp[n-1]+1)%mod*inv2%mod)%mod;
ans=(ans+1ll*dp[n-1]*(dp[n-1]+1)%mod*(dp[n-1]+2)%mod*inv6%mod)%mod;
ans=(2ll*ans%mod+mod-1)%mod;
rep(k, 0, n-1) ans=(ans+1ll*(dp[k]+mod-1)%mod*dp2[n-k-1]%mod)%mod;
writc(ans);
return 0;
}