题解:AT_agc058_f [AGC058F] Authentic Tree DP

· · 题解

不朽的思维题!

本题解会使用 CF Hint 的风格。

::::info[Hint 1]

如果把 \frac{1}{n} 换成 \frac{1}{n-1} 应该怎么做?

:::success[答案]

对于任意树,f(t)=1

:::

:::warning[证明思路]

完全归纳法。

对于单点,f(t)=1

对于大小为 2 的树,将其拆成两个单点,得 f(t)=1

……

对于大小为 n 的树,沿任意一条边分割成两部分,这两部分均满足 f=1,所以答案为 (n-1)\times \frac{1}{n-1}\times 1\times 1=1

:::

::::

::::info[Hint 2]

如何通过改动树的形态,使其能够利用 \frac{1}{n-1} 在上轮计算的特性解决原问题?

:::warning[尝试]

首先,只在每一个边上建一个点的思路是不优的。因为树的总结点数会变成 2n-1。其他很多题解都说要给这些在边上建的点各连 -1 个点,点数恢复至 n。若不好理解可换成 p-1 个点(下文均使用连 p-1 个点的情况)。因为它们模 p 的余数是相等的!

考虑随机一个长度为 (n-1)p+n 的排列,求边上建立的点的权值大于周围所有点(包括原树点和其他的新增点)的概率。

:::

:::warning[为什么转换后的答案与转换前相同?]

令新的树为 W,原树为 T。新的答案为 g(W),原答案为 f(T)

当树为单点时,显然 g(W)=f(T)。树不为单点时,可以使用类似于上文 \frac{1}{n-1} 的证明思路。

:::

::::

:::::info[Hint 3]

考虑用树上背包解决,并加入容斥思路。

::::success[最终解法]

dp_{u,i} 代表节点 u 的外向树大小为 i 的概率。

:::success[以边上新增点为父亲的那 p-1 个新点]

仅有 dp_{u,1}=1

:::

:::success[边上新增点]

为什么我们最开始说的是连 -1 个点?是因为无论是连 -1 个点还是连 p-1 个点都会使 size_uu 的儿子中唯一在原树上的点 vsize 一致。

::: :::success[原树点] 容斥。 将边从父亲指向儿子改成**删除边减去儿子指向父亲**。 删除边需要满足子树 $v$ 合法,$f_{u,i}=f_{u,i}\times \sum f_{v,j}$。 接着就是减去儿子指向父亲的情况,$f_{u,i+j}=f_{u,i+j}-f_{u,i}\times f_{v,j}$。通过改变转移顺序可以节省一倍空间。 还是要乘上 $\frac{1}{i}$。因为还是有这里最大值的要求。 ::: 最终答案:$\sum f_{1,i}$。 时间复杂度 $O(n^2)$。需要预处理逆元。 :::: ::::: :::success[代码] ```cpp #include<bits/stdc++.h> #define int long long #define mp(a,b) make_pair(a,b) using namespace std; inline int read(){ int a=0,b=1; char c=getchar(); while(!isdigit(c)){ if(c=='-') b=-1; c=getchar(); } while(isdigit(c)){ a=(a<<1)+(a<<3)+(c-'0'); c=getchar(); } return a*b; } inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>=10) write(x/10); putchar(x%10+48); } inline void write1(int x){ write(x),putchar(' '); } inline void write2(int x){ write(x),putchar('\n'); } const int N=3*2025; const int mod=998244353; inline int qpow(int x,int y){ if(x==0) return 0; if(y==0) return 1; if(y==1) return x; int ret=qpow(x,y/2); ret=ret*ret%mod; if(y&1) ret=ret*x%mod; return ret; } int inv_[N]; inline int inv(int x){ if(inv_[x]) return inv_[x]; return inv_[x]=qpow(x,mod-2); } vector<int> g[N]; int dp[N][N]; //dp[u][i] 代表子树 u 有 i 的大小的概率 int siz[N]; int fa[N]; int n; void DP(int u){ siz[u]=1; dp[u][1]=1; //代表这里有 1 个的概率为 100% for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(fa[u]==v) continue; fa[v]=u; DP(v); int sum=0; for(int j=siz[u];j>=0;j--){ sum=0; for(int k=siz[v];k>=0;k--){ dp[u][j+k]-=dp[u][j]*dp[v][k]%mod*inv(k)%mod; dp[u][j+k]+=mod; dp[u][j+k]%=mod; sum+=dp[v][k]*inv(k)%mod; sum%=mod; } dp[u][j]=dp[u][j]*sum%mod; } siz[u]+=siz[v]; } for(int i=1;i<=siz[u];i++){ dp[u][i]=dp[u][i]*inv(i)%mod; } } #undef int int main(){ #define int long long // cout<<qpow(3,11)<<endl; n=read(); for(int i=1;i<n;i++){ int u=read(),v=read(); g[u].push_back(v),g[v].push_back(u); } DP(1); int include13=0; for(int i=1;i<=n;i++){ include13+=dp[1][i]; include13%=mod; } write2(include13); return 0; } //AGC058F CSP-S2025RP++ ``` :::