AGC058F
忽略转移式中的 \(\frac{1}{n}\) ,形式比较像边分治,每次断一条边,求方案数。
由此可以联想到给每条边记录其时间戳。
考虑去除 \(\frac{1}{n}\) 的影响,将边拆成点,由于 \(\frac1n \equiv \frac{1}{n + (n-1)P} \pmod P\) ,原图点数为 \(n\) ,让每条边新增的点数为 \(P\) 即可,即每条边拆出的点下在连 \(P-1\) 个点。
那么问题变成了给每个点一个随机权值,边点的权值比相邻点都大的概率。
如何计算?
此时的图是一棵有向树,每条边指向较大点,对于 外向树/内向树 答案为 \(\prod\frac{1}{siz_x}\) ,以向上的方向为正,需要特殊处理向下的边。
如果只有一条边可以考虑容斥,即为 两部分概率乘积 - 该边正向时概率。
两部分的贡献都是与子树大小有关的,所以可以放到 DP 里面做,设 \(f_{i,j}\) 为以 \(i\) 为的内向树子树大小为 \(j\) ,转移即为树上背包。
复杂度 \(O(n^2)\) 。
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
using namespace std;
const int N=5010, mod=998244353;
int n,ans,f[N][N],g[N],siz[N],inv[N],fac[N],ifac[N],fa[N];
vector<int> to[N];
int power(ll x,int n){
ll a=1;
while(n){
if(n&1)a=a*x%mod;
x=x*x%mod;
n>>=1;
}
return a;
}
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
void init(){
fac[0]=1;
fo(i,1,n)fac[i]=(ll)fac[i-1] * i % mod;
ifac[n]=power(fac[n],mod-2);
fd(i,n,1)
ifac[i-1]=(ll)ifac[i] * i % mod,
inv[i]=(ll)ifac[i] * fac[i-1] % mod;
}
void dfs(int x){
siz[x]=1;
f[x][1]=1;
for(auto y:to[x])if(y!=fa[x]){
fa[y]=x;
dfs(y);
int sum=0;
fo(i,1,siz[x]+siz[y])g[i]=0;
fo(i,1,siz[y]){
f[y][i]=(ll)f[y][i] * inv[i] % mod;
sum=add(sum,f[y][i]);
}
fd(i,siz[x],1)
fo(j,1,siz[y]){
g[i+j]=((ll)f[x][i] * f[y][j] + g[i+j]) % mod;
}
siz[x]+=siz[y];
fo(i,1,siz[x]){
f[x][i]=((ll)f[x][i] * sum - g[i] + mod) % mod;
}
}
fo(i,1,siz[x])f[x][i]=(ll)f[x][i] * inv[i] % mod;
}
int main(){
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
scanf("%d",&n);
fo(i,2,n){
int x,y;
scanf("%d%d",&x,&y);
to[x].push_back(y);
to[y].push_back(x);
}
init();
dfs(1);
fo(i,1,n)ans=add(ans,f[1][i]);
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号