[CF1528E]Mashtali and Hagh Trees

壹、题目描述 ¶

传送门 to CF.

贰、题解 ¶

观察这种特别的树的形态。

不难发现,最理想的情况是,存在一个根,使得所有树上的边的方向都是形如从根往下指或者从子节点往根指。

我们把这样的树称为 “妙妙树”,举个例子:

graph TB subgraph miaomiao_tree 1 --> 2 1 --> 3 1 --> 4 2 --> 5 2 --> 6 3 --> 7 4 --> 8 4 --> 9 end

但是,除了妙妙树肯定还有其他情况,再来个例子:

graph TB subgraph not_miao_tree 1 --> 2 1 --> 3 4 --> 1 5 --> 4 6 --> 5 7 --> 5 2 --> 8 2 --> 9 3 --> 10 3 --> 11 end

如果我们将 \(1\) 看成树的根,那么,不难发现,从 \(4\) 开始,它的方向就和我们想要的子树方向是相反的,但是,十分烦人的,不妙树也是满足条件的。为了方便表述,我们将 \(4\) 这样的点称为 weige,或者说,反叛的头领?

我们考虑将不妙树和妙妙树分开算,先看看妙妙树:


定义 \(dp_i\) 表示满足 \(n=i\) 的妙妙树个数。

可以转移:

\[dp_i = dp_{i−1}+dp_{i−1} \cdot pdp_{i−2}+\frac{dp_{i−1} \cdot (dp_{i−1}+1)}{2} \\ pdp_i\overset\Delta=\sum_{k=0}^idp_k \]

几个部分:一个是 \(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\)

同理,左子树选择三号、四号...也有同样道理,也就是说,左右子树深度相同时,方案数为

\[\sum_{k=1}^{dp_{i-1}}dp_{i-1}-k+1={dp_{i-1}(dp_{i-1}+1)\over 2} \]

最后一步就是等差数列咯。

进行如是转移,可以 \(\mathcal O(n)\) 得到 \(dp\) 数组。

最后的答案:

\[2\times \left(dp_n+dp_{n-1}{pdp_{n-2}(pdp_{n-2}+1)\over 2}+pdp_{n-2}{dp_{n-1}(dp_{n-1}+1)\over 2}+{dp_{n-1}(dp_{n-1}+1)(dp_{n-1}+2)\over 6}\right)-1 \]

不难发现,答案的形式和转移的形式有点类似,它其实分成多个部分:

  • 只有一个、两个儿子的情况,\(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}\) 号方案,所以这种情况的方案数为

\[\sum_{i=1}^{dp_{n-1}}\sum_{j=i}^{dp_{n-1}}dp_{n-1}-j+1=\sum_{i=1}^{dp_{n-1}}{(1+dp_{n-1}-i+1)(dp_{n-1}-i+1)\over 2}=\sum_{i=1}^{dp_{n-1}}{(dp_{n-1}-i+1)(dp_{n-1}-i+2)\over 2} \\ ={1\over 2}\sum1\times 2+2\times 3+3\times 4+\cdots+dp_{n-1}(dp_{n-1}+1)={1\over 2}\times {dp_{n-1}(dp_{n-1}+1)(dp_{n-1}+2)\over 3} \\ ={dp_{n-1}(dp_{n-1}+1)(dp_{n-1}+2)\over 6} \]

至于乘 \(2\),因为妙妙树可以选择两个方向(向上或者向下)。而 \(-1\) 则是因为妙妙树变成链的情况(出现在只有一个儿子时)不论哪个方向都是同构的,减去一次。


现在考虑不妙树的数量。考察伪根的数量?至少得有一个吧,能否有两个?

如果有两个,则有至少两个点和整棵树的大势相反,不难发现,这两个点必然不能成为朋友。

特别进行说明,当两个伪根都是根的子节点时,它们肯定是可以互相到达的,但是这时的伪根已经不是伪根了,它成为了这棵树的“大势”,换句话说,根的更多的子树的方向和他们相同,而剩下的那个点反而成为了伪根。所以这种情况不考虑。

那么,现在我们发现只有一个伪根了,但是它在哪里?我们不妨将伪根的父亲当作根,显然,如果这个根的儿子除了伪根以外只有一个的话,那么大事不好了,大势居然和伪根的实力势均力敌,这个时候到底谁才是伪根?为了让大势固定,我们必须得让这个根的大势方向的儿子有两个,那么我们考虑一下这样的组合:一个伪根儿子+正好两个大势儿子。

实际上,这个根和两个大势儿子,其实组合成了一个妙妙树,所以我们实际上要算的就是,一个伪根树与一个根恰好有两个儿子的妙妙树的组合。

一个根恰好有两个儿子的妙妙树的情况怎么算?考虑上面使用的 \(dp\) 数组,如果定义 \(dp2_i\) 表示根刚好有两个儿子的妙妙树,那么就有 \(dp2_i=dp_i-dp_{i-1}\).

我们枚举伪根树的深度,和求得的妙妙树数量进行组合即可,即这部分的数量为

\[\sum_{i=0}^{n-1}(dp_i-1)\times dp2_{n-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;
}
posted @ 2021-05-27 17:06  Arextre  阅读(106)  评论(2编辑  收藏  举报