专栏文章
题解:AT_agc058_f [AGC058F] Authentic Tree DP
AT_agc058_f题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minh9a8f
- 此快照首次捕获于
- 2025/12/02 02:23 3 个月前
- 此快照最后确认于
- 2025/12/02 02:23 3 个月前
不朽的思维题!
本题解会使用 CF Hint 的风格。
Hint 1
如果把 换成 应该怎么做?
答案
对于任意树,。
证明思路
完全归纳法。
对于单点,。
对于大小为 的树,将其拆成两个单点,得 。
……
对于大小为 的树,沿任意一条边分割成两部分,这两部分均满足 ,所以答案为 。
Hint 2
如何通过改动树的形态,使其能够利用 在上轮计算的特性解决原问题?
尝试
首先,只在每一个边上建一个点的思路是不优的。因为树的总结点数会变成 。其他很多题解都说要给这些在边上建的点各连 个点,点数恢复至 。若不好理解可换成 个点(下文均使用连 个点的情况)。因为它们模 的余数是相等的!
考虑随机一个长度为 的排列,求边上建立的点的权值大于周围所有点(包括原树点和其他的新增点)的概率。
为什么转换后的答案与转换前相同?
令新的树为 ,原树为 。新的答案为 ,原答案为 。
当树为单点时,显然 。树不为单点时,可以使用类似于上文 的证明思路。
Hint 3
考虑用树上背包解决,并加入容斥思路。
最终解法
令 代表节点 的外向树大小为 的概率。
以边上新增点为父亲的那 个新点
仅有 。
边上新增点
为什么我们最开始说的是连 个点?是因为无论是连 个点还是连 个点都会使 和 的儿子中唯一在原树上的点 的 一致。
仅需继承 即可。但要再乘以一个 。因为要保证目前点的答案最大。
原树点
容斥。
将边从父亲指向儿子改成删除边减去儿子指向父亲。
删除边需要满足子树 合法,。
接着就是减去儿子指向父亲的情况,。通过改变转移顺序可以节省一倍空间。
还是要乘上 。因为还是有这里最大值的要求。
最终答案:。
时间复杂度 。需要预处理逆元。
代码
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++
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...